Skip to content

Planck Workbench, User Manual

The Workbench is the web-based administration interface for Planck. From a single browser tab you can connect to databases, run PQL queries, deploy and supervise services, monitor performance, manage users, take and restore backups, schedule recurring tasks, and browse logs.

This manual walks through every screen in the order you'll meet it. Sections marked (admin only) are visible only to users with the admin role.


Table of Contents

  1. Login
  2. Navigation
  3. Sidebar, Service Tree
  4. Query Editor
  5. Dashboard
  6. Server Overview
  7. Services
  8. Deploying Services
  9. Backups
  10. Schedules
  11. PQL Reference

1. Login

The login screen authenticates you to the System Database.

Fields:

  • Admin User: Defaults to admin
  • Admin Key: Your Base64-encoded API key

First-time setup: Use the factory default key:

UGxhbmNrX0RlZmF1bHRfQWRtaW5fS2V5XzAwMTA=

After the first successful login, the system automatically regenerates your key. A dialog displays the new key, copy and save it, as it cannot be recovered later. Click "Continue" to proceed to the main interface.

Credentials are saved locally for auto-reconnect on subsequent visits.


2. Navigation

The blue header bar at the top provides access to all sections:

ButtonSectionAccess
QueryQuery editor workspaceAll users
ServicesDeployed services tableAll users
DashboardPerformance monitoringAdmin only
SchedulesAutomated task managementAdmin only
SettingsWorkbench configurationAdmin only
PQL RefQuery language documentationAll users
LogoutEnd sessionAll users

When running in a replica configuration, a role badge (command or query) appears next to the logo.


3. Sidebar, Service Tree

The left sidebar displays the hierarchical service tree:

▼ myapp
    ● myapp-command     (green = running)
        users           (store)
        orders          (store)
    ● myapp-query       (green = running)
▼ analytics
    ○ analytics-svc     (grey = stopped)
        events          (store)

Hierarchy:

  1. Apps, groups of related services
  2. Services, individual database instances, each with a status dot
  3. Stores, data namespaces within each service

Status dots: Green = running, Red = crashed, Grey = stopped

Interactions:

  • Click a service to open a query tab
  • Click a store to pre-fill a query against that store
  • Plug icon (hover), connect/disconnect
  • Refresh icon (hover), reload schema
  • Gear icon (hover), open Server Overview admin panel

4. Query Editor

The query workspace supports multiple tabs, one per service connection.

Tabs

Each tab shows a status dot and the service name. Click + to open a new tab, X to close one. Tabs let you keep parallel sessions against different services.

Editor

Write PQL queries in the upper editor pane. Separate multiple queries with ;.

Execute: click the play button, or press Ctrl+R / Cmd+R.

The editor picks the right query to run automatically:

  • If you have text selected, only the selection runs.
  • If your cursor sits inside a query (between two ;), that query runs.
  • Otherwise, the first query in the editor runs.

Results

The lower pane has two views:

Results, a scrollable table. Click any row to expand nested objects. The status bar shows row count, data size, and elapsed time.

JSON, the raw response, pretty-printed. Useful for inspecting errors or copying out a result verbatim.

Status Indicator

  • Yellow pulsing dot, query is executing
  • Green dot, ready, with the last query's row count, data size, and elapsed time
  • Red dot, error, with the message inline

5. Dashboard

Performance monitoring for deployed services. Admin only. 7 tabs:

Overview

KPI cards showing current metrics:

  • Total Reads, Writes, Deletes, Updates
  • Read/Write Latency (microseconds)
  • WAL Bytes Written
  • VLog Size
  • Dead Ratio (reclaimable space percentage)

Line charts showing trends over time.

Operations

Four charts: DB Operations (reads/writes/updates/deletes), WAL Operations (appends/fsyncs/flushes), Index Operations (inserts/searches/scans), VLog Operations (reads/writes/GC runs).

Latency

Two charts: Read Latency and Write Latency trends over time.

VLogs

Summary cards: Total VLog count, entries, sizes (total/live/dead).

VLog table with columns: ID, Entries, Deleted, Total Size, Live Size, Dead Size, Dead Ratio, Tail flag.

The Dead Ratio column shows a color-coded bar so you can spot reclaim candidates at a glance, green below 40%, orange 40–70%, red at 70% and above. Files in the red band typically benefit most from a GC run (see Schedules).

HTTP Stats

Per-method HTTP request metrics for WASM services. Shows total request count and a table of method, count, average latency, and a distribution bar. Use the auto-refresh control (5s / 10s / 30s / off) to keep the view live during a load test.

WASM Stats

Health and utilization for the WASM instance pool of the selected service:

  • Gauge cards for active instances, pool size, and recycled count
  • Progress bars showing utilization against the configured maximum
  • A pool grid where each slot is colored, green active, blue idle, grey empty, for a quick read of pool pressure
  • The configured min and max instance counts
  • Average request latency across the pool

Logs

Split-pane log file viewer:

  • Left panel: Tree navigation (Apps > Services > Log files)
  • Right panel: Line-numbered file viewer with monospace font
  • Search: Enter a keyword, matching lines highlighted in yellow
  • Log level coloring: ERROR = red, WARN = amber
  • Pagination: 1000 lines per page, Previous/Next navigation

6. Server Overview

Full-screen admin panel for a single service. Open via the gear icon in the sidebar.

Schema Tab

Browse stores and indexes:

  • Expandable tree listing all stores and their indexes
  • Create Store button (admin only)
  • Drop Store via right-click context menu

Users Tab (admin only)

Manage database users:

  • Table: Username, Role, Actions
  • Create User: Username, role selection (admin/read_write/read_only), optional custom key
  • Regenerate Key: Generates a new API key, shown in a modal, copy immediately
  • Update Role: Inline dropdown per user
  • Delete User: With confirmation

Config Tab (admin only)

  • YAML editor for the service configuration
  • Save / Reset buttons

Backups Tab (admin only)

See Backups section.

Service Controls (top bar)

Running status indicator, Start / Stop / Restart / Undeploy buttons, Back button.


7. Services

Table of all deployed services:

ColumnDescription
NameService identifier
AppParent app name
StatusRunning (green), Stopped (grey), Crashed (red)
PortTCP port
Rolecommand / query / standalone

Row actions (hover): Start, Stop, Restart, Undeploy, Overview.


8. Backups

Within the Server Overview panel for each service.

  • Create Backup: Enter a directory path, click Create
  • Backup List: Table showing path, size, creation timestamp
  • Restore: Button per backup (optionally specify target path)
  • Delete: With confirmation dialog

9. Schedules

Automated recurring tasks. Admin only.

Task Types

TypeDescription
BackupPeriodic backup to specified path
GCGarbage collection, reclaim dead VLog space
WAL TruncateTruncate write-ahead log segments
ExportData export via manifest
ImportData import via manifest

Creating a Schedule

  1. Name, Service (dropdown), Task Type
  2. Cron Expression, text input with preset buttons:
    • Every 1 min, Every 5 min, Hourly, Daily 2am, Daily 4am, Weekly Sunday 3am
  3. Backup Path (for backup tasks)
  4. Description (optional)
  5. Enable/disable toggle

Managing Schedules

  • Toggle enabled/disabled per task
  • Edit any field
  • Delete with confirmation

10. PQL Query Language

PQL is Planck's text query language used from the workbench and shell.

Syntax: store.operation(...)

All examples use the AdventureWorks sample dataset (import via the Import feature):

StoreKey Fields
ordersEmployeeID (int), CustomerID (int), TotalDue (float)
employeesEmployeeID (int), Gender (string), MaritalStatus (string)
productsProductName (string), ListPrice (float), SubCategoryID (int), MakeFlag (int)
customersCustomerID (int), Address.City (string), Address.State (string)
vendorsVendorName (string), ActiveFlag (int), CreditRating (int)
productcategoriesCategoryName (string)

1. Count

orders.count()
orders.filter(EmployeeID = 289).count()
vendors.filter(ActiveFlag = 1).count()
products.filter(MakeFlag = 1).count()

2. Filter, Equality

employees.filter(Gender = "M").count()
employees.filter(EmployeeID = 274).count()
products.filter(SubCategoryID = 14).count()
productcategories.filter(CategoryName = "Bikes").count()

3. Filter, Comparison Operators

Operators: > < >= <= !=

orders.filter(TotalDue > 50000).count()
orders.filter(TotalDue < 100).count()
orders.filter(TotalDue >= 100000).count()
products.filter(ListPrice > 1000).count()
vendors.filter(CreditRating != 1).count()

4. Compound Filters (AND)

orders.filter(EmployeeID = 289 and CustomerID = 1045).count()
employees.filter(Gender = "M" and MaritalStatus = "M").count()
orders.filter(EmployeeID >= 285 and EmployeeID <= 287).count()

5. Limit & Skip

orders.limit(10).count()
orders.skip(3800).count()
customers.limit(100).count()

6. OrderBy (Sorting)

products.orderBy(ListPrice, desc).limit(5)
products.orderBy(ListPrice, asc).limit(5)
employees.orderBy(EmployeeID, asc).limit(3)
employees.orderBy(EmployeeID, desc).limit(3)

7. Multi-Sort

orders.orderBy(EmployeeID, asc).orderBy(TotalDue, desc).limit(10)
employees.orderBy(Gender, asc).orderBy(EmployeeID, desc).limit(10)

// With filter
orders.filter(EmployeeID >= 285).orderBy(EmployeeID, asc).orderBy(TotalDue, asc).limit(20)

8. Projection (pluck)

Return only specific fields:

employees.filter(EmployeeID = 274).pluck(EmployeeID, FullName)
products.filter(SubCategoryID = 14).limit(1).pluck(ProductName, ListPrice)
employees.limit(1).pluck(EmployeeID)
orders.filter(EmployeeID = 289).orderBy(TotalDue, desc).limit(1).pluck(EmployeeID, TotalDue)

9. Aggregation, Count

orders.aggregate(total: count)
orders.filter(EmployeeID = 289).aggregate(total: count)
products.filter(MakeFlag = 1).aggregate(n: count)

10. Aggregation, Sum, Avg, Min, Max

orders.aggregate(total: sum(TotalDue))
orders.aggregate(avg_total: avg(TotalDue))
orders.aggregate(min_total: min(TotalDue))
orders.aggregate(max_total: max(TotalDue))

// With filter
orders.filter(EmployeeID = 289).aggregate(revenue: sum(TotalDue))

11. GroupBy

orders.groupBy(EmployeeID).aggregate(n: count)
employees.groupBy(Gender).aggregate(n: count)
employees.groupBy(Gender, MaritalStatus).aggregate(n: count)
orders.groupBy(EmployeeID).aggregate(n: count, total: sum(TotalDue))

12. Filter + GroupBy

orders.filter(TotalDue > 10000).groupBy(EmployeeID).aggregate(n: count)
products.filter(ListPrice > 0).groupBy(SubCategoryID).aggregate(n: count, avg_price: avg(ListPrice))
orders.filter(EmployeeID = 289).groupBy(CustomerID).aggregate(n: count, total: sum(TotalDue))

13. $in Operator

orders.filter(EmployeeID in [289, 288]).count()
orders.filter(EmployeeID in [289, 287, 285]).count()
products.filter(SubCategoryID in [1, 2, 14]).count()
employees.filter(Gender in ["M"]).count()

14. $contains Operator

products.filter(ProductName contains "Road").count()
products.filter(ProductName contains "Mountain").count()
products.filter(ProductName contains "Frame").count()
vendors.filter(VendorName contains "Bike").count()

15. $startsWith Operator

products.filter(ProductName startsWith "HL").count()
products.filter(ProductName startsWith "Mountain").count()
employees.filter(FirstName startsWith "S").count()

16. $exists Operator

products.filter(ProductName exists true).count()
employees.filter(Gender exists true).count()

17. $regex Operator

Use ~ as the regex operator:

products.filter(ProductName ~ "^HL").count()
products.filter(ProductName ~ "Frame").count()
products.filter(ProductName ~ "58$").count()
products.filter(ProductName ~ "^AWC Logo Cap$").count()

18. OR Filters

employees.filter(Gender = "M" or MaritalStatus = "S").count()
products.filter(ProductName contains "Road" or ProductName contains "Mountain").count()
products.filter(SubCategoryID = 1 or SubCategoryID = 2).count()
orders.filter(TotalDue > 100000 or TotalDue < 100).count()
orders.filter(EmployeeID = 289 or EmployeeID = 288).count()

19. Range Scans

// Closed range
orders.filter(EmployeeID >= 285 and EmployeeID <= 287).count()

// Open range
orders.filter(EmployeeID > 285 and EmployeeID < 289).count()

// One-sided
orders.filter(EmployeeID > 288).count()
orders.filter(EmployeeID < 285).count()

20. $between Operator

Inclusive range match, equivalent to field >= lower and field <= upper, but more concise and directly mapped to a B+ tree range scan when the field is indexed:

orders.filter(TotalDue between 100 and 5000).count()
products.filter(ListPrice between 10.0 and 50.0).count()
employees.filter(EmployeeID between 280 and 290).count()

// Combined with other conditions
products.filter(ListPrice between 10.0 and 50.0 and MakeFlag = 1).count()
orders.filter(EmployeeID between 285 and 289).orderBy(TotalDue, desc).limit(10)

Both bounds are inclusive. Works on numeric fields.


21. Nested Field Access

Use dot notation for embedded documents:

customers.filter(Address.City = "New York").count()
customers.filter(Address.State = "CA").count()
customers.filter(Address.Country = "US").count()
customers.filter(Address.City = "Seattle").count()

22. Insert

products.insert({"ProductID": 9001, "ProductName": "Test Widget", "ListPrice": 99.99, "SubCategoryID": 1})
vendors.insert({"VendorID": 9001, "VendorName": "Test Vendor", "CreditRating": 3, "ActiveFlag": 1})
productcategories.insert({"CategoryID": 99, "CategoryName": "TestCategory"})

23. Update (set)

Use .filter().set({fields}), only specified fields are updated:

products.filter(ProductID = 9001).set({"ListPrice": 149.99})
vendors.filter(VendorName = "Test Vendor").set({"CreditRating": 5})
products.filter(MakeFlag = 1 and ListPrice > 100).set({"StandardCost": 75.00})
products.filter(ProductID = 9001).set({"ListPrice": 199.99, "StandardCost": 80.00})

24. Delete

products.filter(ProductID = 9001).delete()
vendors.filter(ActiveFlag = 0).delete()
products.filter(MakeFlag = 0 and ListPrice < 5).delete()

Operator Reference

Filter Operators

OperatorSyntaxDescription
Equal=Exact match
Not Equal!=Not equal
Greater Than>Greater than
Greater or Equal>=Greater than or equal
Less Than<Less than
Less or Equal<=Less than or equal
Inin [...]Value in list
Betweenbetween A and BInclusive range
Containscontains "..."Substring match
Starts WithstartsWith "..."Prefix match
Existsexists trueField exists check
Regex~ "pattern"Regex match

Logical Operators

OperatorSyntax
ANDand
ORor

Aggregation Functions

FunctionSyntax
Countaggregate(alias: count)
Sumaggregate(alias: sum(field))
Averageaggregate(alias: avg(field))
Minimumaggregate(alias: min(field))
Maximumaggregate(alias: max(field))

Query Modifiers

ModifierSyntax
Limit.limit(N)
Skip.skip(N)
Order By.orderBy(field, asc/desc)
Group By.groupBy(field)
Projection.pluck(field1, field2)
Count Only.count()

Query Chaining Order

space.store
  .filter(...)           // optional filter (and/or)
  .orderBy(field, dir)   // optional sort (chainable)
  .limit(N)              // optional limit
  .skip(N)               // optional offset
  .pluck(field1, ...)    // optional projection
  .count()               // count only
  .aggregate(...)        // aggregation
  .groupBy(field)        // group by
  .insert({...})         // mutation: insert
  .set({...})            // mutation: update
  .delete()              // mutation: delete

Secondary Index Usage

Queries automatically use secondary indexes when available:

  • Equality (=), exact key lookup
  • Range (>, >=, <, <=), B+ tree range scan
  • Between (between A and B), B+ tree range scan (inclusive)
  • $in, multi-key lookup

Operators that always require full scan:

  • contains, startsWith, ~ (regex)
  • exists
  • !=
  • Queries with no filter

For the full client-side query API and language details, see the Planck client documentation for your language of choice on https://plancks.io/docs.