Trace Datasette Query Performance

Trace Datasette queries locally and use spans to speed up a slow SQL query.

Introduction

30 Second Summary

When a page feels slow, the delay could come from several places. Without a clear timeline, finding the cause becomes guesswork.

In this project, you will trace requests from Datasette 1.0a41 as it explores a sample SQLite database. A local OpenTelemetry viewer reveals the slowest query so you can measure the effect of an index.

What You'll Build

You'll open a request in the trace viewer to see exactly where its time went, with a faster follow-up trace proving your index worked.

By the end of this project, you'll have:

  • A live local data browser where you can explore a sample database through table pages.
  • A trace timeline that reveals the database work behind each request.
  • A before-and-after comparison showing how an index changes the slowest query's timing.
  • Secret Mission: An optional challenge to push your tracing skills further.

Are there any prerequisites?

You'll need Python plus either pip or uv. The database, app, and trace viewer all run locally at no cost.

Before We Start

Before the hands-on work begins, this is your chance to lock in what you are building and why tracing matters. That purpose gives every query you investigate a clear direction.

Run Datasette with a Sample Database

Tracing becomes useful when an application has real requests to follow. Your local Datasette instance gives those requests a concrete SQLite database workload.

First, you'll pin the Datasette release used throughout this project. You'll also create a sample database that you can browse locally.

In this step, get ready to:
  • Install Datasette 1.0a41 in your active Python environment.
  • Generate a populated sample.db database.
  • Browse the items table through the local Datasette interface.
Install Datasette 1.0a41

The project pins 1.0a41 because this alpha release introduced OpenTelemetry support. The fixed version keeps the later telemetry steps aligned with the same release.

  • Check whether Datasette is already available by running this command:
datasette --version

What does this command do?

The command asks the Datasette CLI to print its installed version. That output tells you which setup path to follow.

✔️ I see the required version

You're ready. Datasette 1.0a41 is already installed in the active Python environment.

ⓧ I see another version

Another Datasette release can behave differently during the telemetry steps. Installing the pinned release gives you the expected behavior.

  • Replace the installed release with the pinned release by running this command:
pip install datasette==1.0a41

What does this command do?

The == requirement tells pip to install one exact release. This prevents a different Datasette version from changing the project behavior.

  • Confirm the installed release by running the version check again:
datasette --version

What does this check prove?

The version check reads the Datasette executable available in your active environment. You should now see 1.0a41 in the output.

The pinned release is now active. Your later telemetry results come from the release this project targets.

ⓧ Command not found

The Datasette command is unavailable in the active Python environment. Installing the pinned package adds the CLI that this project uses.

  • Install the pinned Datasette release by running this command:
pip install datasette==1.0a41

What does this command do?

The command downloads Datasette 1.0a41 plus its required Python packages. It installs them into the environment connected to pip.

  • Confirm the installation by running the version check:
datasette --version

What does this check prove?

A successful check confirms that the terminal can find Datasette. You should see 1.0a41 in the output.

That's the installation complete. The Datasette command is now ready for your database.

Installation still failing?

Confirm that pip belongs to the same Python environment where you are running Datasette. A different active environment can hide a successful installation.

Help me fix this Datasette installation. Ask the Datasette community.

Create the sample database

The database needs a table that produces useful page requests. A repeated category pattern also gives the table filters visible data to work with.

  • Move your terminal to the Desktop by running this command:
cd ~/Desktop

What does this command do?

This changes the terminal's current location to your Desktop. The database created next lands somewhere you can find easily.

  • Generate sample.db with its populated items table by running this command:
python -c "import sqlite3; con = sqlite3.connect('sample.db'); con.execute('CREATE TABLE items (id INTEGER PRIMARY KEY, category TEXT, name TEXT, description TEXT)'); con.executemany('INSERT INTO items (category, name, description) VALUES (?, ?, ?)', [(f'category-{i % 10}', f'Item {i}', f'Sample description for item {i}') for i in range(1, 10001)]); con.commit(); con.close()"

What does this command create?

  • The sqlite3.connect() call creates sample.db on your Desktop.
  • The table definition makes id the primary key.
  • The remaining text columns are category, name, plus description.
  • The executemany() call adds 10,000 sample items across ten categories.
  • Confirm that the database file exists by running:
ls

What does this check show?

The command lists the files on your Desktop. You should see sample.db in the output.

Database file missing?

Check that the terminal is still on your Desktop. Rerun the database command if sample.db is absent from the file list.

Help me create the sample SQLite database.

Browse the items table

Datasette turns the SQLite file into a local website. The server stays connected to your terminal while you browse the data.

Before you start the server, what do you expect the browser to show for a database named sample.db?

  • Start Datasette against sample.db by running this command:
datasette sample.db -o

What does this command do?

  • The command attaches sample.db to the local Datasette server.
  • The -o option opens the web interface in your browser.
  • The server keeps running in this terminal while you use the website.

Your browser should open the Datasette home page at http://localhost:8001/. You should see the sample database listed.

  • Click sample on the Datasette home page.

Before you select items, which columns do you expect to see from the schema you created?

  • Click items in the table list.

You should see rows numbered by id. The table header should show category, name, plus description.

That is your first visible win: Datasette is serving a real table from your own SQLite file.

Browser did not open?

Keep the Datasette process running in the terminal. Visit http://localhost:8001/ directly in your browser.

Help me open my local Datasette interface.

✔️ Awesome, I've got everything!

Great. Your items table is visible through the local Datasette interface.

ⓧ I'd like to double check the full code

Datasette 1.0a41 is running locally with sample.db attached and is accessible in a web browser.

Local SQLite sample database containing an items table with id as its primary key and category, name, and description columns.

Your sample database is live in Datasette. Next, you'll start the local trace viewer that collects request data.

Start the Trace Viewer

Your sample database is now running in Datasette. You can browse the items table in your browser.

A trace needs a separate place to land before you can inspect it.

You'll run Jaeger inside a Docker container. Its empty viewer proves the destination is ready.

In this step, get ready to:
  • Confirm Docker is ready.
  • Run Jaeger all-in-one.
  • Confirm the Jaeger UI starts with an empty trace list.
Check Docker

Docker runs the trace viewer without adding Jaeger directly to your Python environment. The version check also confirms that Docker's background engine can respond.

  • Create a new tab in the terminal from earlier so Datasette keeps running in its current tab.
  • Check whether Docker's client can reach its engine by running this command:
docker version

What does this check?

This check requests version information from Docker's command-line client. It also requests details from the background engine.

A healthy response includes both Client and Server sections. That result proves Docker can start containers.

✔️ I see Client and Server details

Good, your Docker client can reach its engine. The trace viewer now has an isolated place to run.

ⓧ Docker Engine is not running

The Docker client is installed. Its background engine still needs to start.

  • Press Cmd+Space on macOS or the Windows key on Windows to open system search.
  • Type Docker Desktop into the search bar.
  • Press Enter to start Docker Desktop.
  • Wait until Docker Desktop reports that its engine is running.
  • Return to the terminal tab from earlier.
  • Repeat the version check above.

ⓧ Command not found

Docker is not installed yet. Choose your operating system to install Docker Desktop.

macOS

  • Open Docker's macOS installation guide in your browser.
  • Download the installer that matches your Mac processor.
  • Double-click Docker.dmg to start the installer.
  • Drag the Docker icon into Applications.
  • Press Cmd+Space to open system search.
  • Type Docker Desktop into the search bar.
  • Press Enter to start Docker Desktop.

Windows

  • Open Docker's Windows installation guide in your browser.
  • Download the Docker Desktop installer.
  • Double-click Docker Desktop Installer.exe to start the installer.
  • Choose the recommended per-user installation mode.
  • Complete the installer.
  • Press the Windows key to open system search.
  • Type Docker Desktop into the search bar.
  • Press Enter to start Docker Desktop.
  • Return to the terminal tab after Docker Desktop finishes starting.
  • Repeat the version check above.

Still unable to start Docker?

  • Keep Docker Desktop open until its background engine finishes starting.
  • Compare your computer against the system requirements in the installation guide for your operating system.

Help me diagnose why Docker Desktop is installed but its engine is unavailable.

Prepare the trace destination

Jaeger all-in-one combines its trace collector with its browser viewer in one process. It keeps this project's trace data in temporary memory.

The first launch may take a few minutes while Docker downloads the image. The incoming layer messages show that the download is progressing.

  • Keep the original Datasette terminal tab running.
  • Start Jaeger in the new terminal tab by running this command:
docker run --rm --name jaeger \
  -p 16686:16686 \
  -p 4317:4317 \
  -p 4318:4318 \
  -p 5778:5778 \
  -p 9411:9411 \
  cr.jaegertracing.io/jaegertracing/jaeger:2.20.0

What does this command do?

  • The --name jaeger option gives the container a recognizable name.
  • The --rm option removes the temporary container after it stops.
  • Port 16686 publishes the Jaeger web interface to your computer.
  • Port 4318 publishes the HTTP receiver for incoming trace data.
  • The 2.20.0 image tag keeps the viewer version consistent.
  • Leave the Jaeger terminal tab running.
  • Confirm the process stays active after its startup logs settle.

Did the Jaeger container stop?

  • Start Docker Desktop if the background engine stopped.
  • Stop any local service that Docker identifies as using one of Jaeger's published ports.
  • Check your internet connection if the image download stops before completing.

Help me troubleshoot why my Jaeger container exits during startup.

Confirm the empty viewer

The browser interface checks that Jaeger's query side is available. The OpenTelemetry Protocol HTTP receiver waits separately for trace data.

What are the two endpoints for?

  • The http://localhost:16686 address serves the Jaeger UI.
  • The http://localhost:4318/v1/traces endpoint receives traces over HTTP.

Before you open the viewer, do you expect any Datasette requests to be visible yet?

  • Switch back to the browser from earlier.
  • Create a new browser tab.
  • Enter http://localhost:16686 in the address bar.
  • Press Enter to load the Jaeger UI.

You'll see the Jaeger UI with no traces listed. That empty result is the intended baseline because Datasette is not sending telemetry yet.

Can't open the Jaeger UI?

  • Confirm the Jaeger terminal tab is still running.
  • Check that the browser address uses port 16686.
  • Rerun the container command if the previous process exited.

Help me find out why the Jaeger UI does not load on my computer.

Your trace viewer is running with an empty baseline. Next, you'll connect Datasette to the receiver so your first request appears as a trace.

Send a Datasette Trace

Your local Jaeger viewer is ready. Its empty trace list shows that Datasette is not sending telemetry yet.

This step connects Datasette 1.0a41 to Jaeger with OpenTelemetry. A browser request then becomes a searchable trace containing the database work behind that page.

In this step, get ready to:
  • Install the OpenTelemetry SDK and exporter.
  • Restart Datasette through the instrumentation agent.
  • Send a page request and find its trace in Jaeger.
Prepare the telemetry toolchain

Datasette already includes telemetry hooks. An OpenTelemetry SDK records those signals while an exporter sends them to an OTLP endpoint.

  • Switch back to the terminal where Datasette is running.
  • Stop the current Datasette process by pressing Ctrl+C.
  • Install the OpenTelemetry SDK and exporter by running this command:
pip install opentelemetry-distro opentelemetry-exporter-otlp

What does this command install?

  • The opentelemetry-distro package provides the SDK configuration and instrumentation agent.
  • The opentelemetry-exporter-otlp package sends telemetry to an OTLP-compatible receiver such as Jaeger.
  • Wait for the package installation to finish.

That is the telemetry toolchain installed. Your active Python environment can now run Datasette through the instrumentation agent.

Did the installation fail?

  • Confirm that the terminal still has the Python environment from the earlier Datasette installation active.
  • Check your internet connection if the package download stops before completion.
  • Run the installation command again after closing any output from the stopped Datasette process.

Ask for help with the installation error.

Connect Datasette to Jaeger

The instrumentation agent creates the SDK configuration before Datasette starts. Environment variables give the service a searchable name and direct its traces to Jaeger's HTTP receiver.

  • Start Datasette against sample.db with tracing enabled by running this command:
OTEL_SERVICE_NAME=datasette \
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4318/v1/traces \
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/protobuf \
OTEL_METRICS_EXPORTER=none \
OTEL_LOGS_EXPORTER=none \
OTEL_TRACES_EXPORTER=otlp \
OTEL_BSP_SCHEDULE_DELAY=1000 \
opentelemetry-instrument datasette sample.db

What does this launch do?

  • The OTEL_SERVICE_NAME=datasette setting files each trace under a recognizable service name.
  • The OTEL_EXPORTER_OTLP_TRACES_ENDPOINT setting sends trace data to http://localhost:4318/v1/traces.
  • The OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/protobuf setting selects the transport accepted by the local receiver.
  • The OTEL_METRICS_EXPORTER=none setting disables metric export for this tracing exercise.
  • The OTEL_LOGS_EXPORTER=none setting disables log export for this tracing exercise.
  • The OTEL_TRACES_EXPORTER=otlp setting activates the OTLP trace exporter.
  • The OTEL_BSP_SCHEDULE_DELAY=1000 setting shortens the wait before completed spans are exported.
  • The opentelemetry-instrument agent starts Datasette with the configured telemetry SDK.
  • Keep this terminal running while you generate the trace.

You should see Datasette complete its startup and continue running against sample.db. That restart closes the wiring gap between the application and Jaeger.

Did Datasette stop during startup?

  • Confirm that sample.db is in the terminal's current folder.
  • Confirm that the OpenTelemetry installation completed in the same active Python environment.
  • Check that the terminal did not split or remove a backslash from the multi-line command.

Ask for help with the traced Datasette launch.

Find the first trace

Before you refresh, do you expect one page load to create one span or a trace containing several spans?

  • Switch back to the Datasette browser tab from earlier.
  • Refresh the items table page.

The items table should load normally. That request now passes through the tracing layer before Datasette returns the page.

A short delay here is normal. The batch processor needs a moment to send the completed spans.

  • Wait a few seconds for the trace batch to reach Jaeger.
  • Switch back to the Jaeger browser tab from earlier.
  • Refresh the Jaeger page.
  • Select datasette in the Service field.
  • Click Find Traces.
  • Open the newest trace in the results.

You should see at least one Datasette request trace. Its expanded timeline contains the request span plus child db.query spans for database work.

You have a live telemetry path. Jaeger can now show where a Datasette request spends its time.

Still seeing no traces?

  • Confirm that the terminal still shows Datasette running through the instrumentation agent.
  • Confirm that the Jaeger container still exposes http://localhost:4318/v1/traces.
  • Refresh the Datasette page again before repeating the Jaeger search.

Ask for help tracing the missing connection.

Your first request is visible from browser click to database work. Next, you'll generate filtered and faceted views so you can compare their traces.

Compare Table View Traces

Your first Datasette trace proved that telemetry reaches Jaeger. You can now use that trace data to investigate real requests.

This step creates a filtered table request plus a faceted table request. You will compare their request durations with their database-related span durations.

In this step, get ready to:
  • Load a filtered view of the items table.
  • Load a category facet for the same table.
  • Compare request durations with database-related span durations in Jaeger.
Load a filtered table view

A filter narrows the rows returned by the table request. Loading that result gives you a trace for one specific database workload.

  • Switch back to the Datasette items table from earlier.
  • Pick one value from the visible category column.
  • Store the chosen value here: a category shown in your table.
  • Select category in the table filter's column control.
  • Leave the comparison control on equality.
  • Enter a category shown in your table in the filter value field.
  • Apply the filter using the submit control beside the filter fields.

You should see fewer rows. Every visible row should have a category shown in your table in its category column.

Good work. Your filtered request now has a distinct trace that you can measure.

  • Copy the current URL from your browser address bar.
  • Store the copied URL here: your filtered items URL.

Filter does not narrow the rows?

  • Check that the selected column is category.
  • Copy the category value exactly as it appears in the table.
  • Remove any extra spaces from the filter value.

Ask for help with the filter controls: Why is my Datasette category filter returning unexpected rows?

Load a faceted table view

A facet summarizes common values with their row counts. Datasette may run additional database queries to calculate that summary.

  • Replace the query string in the current browser address with ?_facet=category.
  • Press Enter to load the faceted table view.

You should see a category summary beside the table. Each listed category should include a row count.

  • Copy the current URL from your browser address bar.
  • Store the copied URL here: your faceted items URL.

Why can facets take longer?

The table request retrieves rows for display. A facet can add database work to calculate distinct values plus their counts.

The trace shows whether that extra work affects this request. Your measurements provide the answer for your data.

Facet summary missing?

  • Confirm that the URL ends with ?_facet=category.
  • Replace the old query string instead of appending a second question mark.
  • Reload the table after correcting the URL.

Ask for help with the facet URL: Why is the category facet missing from my Datasette table view?

Compare request and database spans

The outer request duration measures the complete page request. The database-related spans isolate the time spent on database or SQL work.

  • Load your filtered items URL in the Datasette browser tab.
  • Return to the Jaeger UI from earlier.
  • Run the trace search to refresh the results.
  • Open the trace whose start time matches the filtered request you just loaded.
  • Store the total request duration here: filtered request duration.
  • Expand the span rows that describe database or SQL work.
  • Identify the longest database-related span by comparing the duration labels.
  • Store its duration here: filtered database span duration.

What do these durations show?

The request duration covers all traced work needed to produce the page. The database span duration covers one database operation inside that request.

Comparing both measurements helps you separate database time from the rest of the request.

Before you load the facet again, which request do you expect to have the larger database-related span?

  • Load your faceted items URL in the Datasette browser tab.
  • Return to the Jaeger UI.
  • Run the trace search to refresh the results.
  • Open the trace whose start time matches the faceted request you just loaded.
  • Store the total request duration here: faceted request duration.
  • Identify the longest database-related span by comparing the duration labels.
  • Store its duration here: faceted database span duration.

Read the result from the trace

The faceted trace may contain more database work because the page calculates category counts. The exact timing depends on your local data plus your computer.

Your trace timeline shows the measured result. Use those durations as evidence.

  • Compare filtered request duration with faceted request duration.
  • Compare filtered database span duration with faceted database span duration.

You should now have two measured request totals. You should also have the longest database-related span duration for each view.

The larger total identifies the slower page request. The database spans show whether database work contributed to that difference.

Cannot find the latest trace?

  • Confirm that the Datasette process from earlier is still running.
  • Confirm that the Jaeger container from earlier is still running.
  • Reload the table view after refreshing the Jaeger trace search.

Ask for help finding the matching request: How can I identify my latest Datasette request trace in Jaeger?

You can now compare table requests using measured evidence. Next, you will create a deliberately slow query and test whether an index improves its trace.

Optimize the Slow Query

You have already compared filtered requests with faceted requests in Jaeger. Those traces gave you a measured baseline for Datasette.

Now you will turn that baseline into an optimization test. The same query runs before and after one composite index, so the traces show whether the change helped.

In this step, get ready to:
  • Capture a pre-index trace for the deliberately slow query.
  • Create an index for the query's filter and sort columns.
  • Compare the two traces in a local note.
Trace the slow query

A baseline trace captures the query before any optimization. This gives you evidence that you can compare with the result after the database changes.

  • Switch back to the Datasette browser tab from earlier.
  • Return to the sample database page.
  • Find the SQL editor on the database page.
  • Replace the editor contents with this query:
SELECT id, category, name, description FROM items WHERE category = 'example-category' ORDER BY name

What does this query request?

  • The selected columns return each matching item's identifier plus its category plus its name plus its description.
  • The WHERE category = 'example-category' condition limits the results to one category.
  • The ORDER BY name clause sorts the matching rows by name.

Before you run it, where do you expect this request to spend most of its time?

  • Run the query with the SQL editor's query control.
  • Confirm the result table shows rows from example-category ordered by name.

No rows or trace?

  • Check that the category value is spelled exactly as example-category.
  • Confirm Datasette is still running in the original terminal tab.
  • Refresh the trace search after the query finishes.

Help me find why the slow query is not returning rows or producing a trace.

  • Switch back to the Jaeger browser tab from earlier.
  • Repeat the trace search process you used in the previous step.
  • Open the newest Datasette request trace for the SQL query.
  • Expand the database-related span that executed the query.
  • Record its duration as your pre-index span duration.

Good, you now have a pre-index measurement. This should be the slowest database-related span among the queries you tested.

Why did the query take longer?

The SQLite database has no index that starts with category before continuing to name. SQLite must inspect rows for the category filter before producing the requested name order.

A composite index on those columns gives SQLite an ordered path through the matching rows. The next trace measures the impact.

Add the matching index

The Datasette SQL editor accepts read-only queries. Creating an index changes the database schema, so you will make that change from a second terminal tab while Datasette keeps running.

  • Open a second tab in the terminal window using its new-tab control.
  • Keep the original terminal tab running Datasette.
  • Create idx_items_category_name on sample.db by running this command in the second terminal tab:
python -c "import sqlite3; connection = sqlite3.connect('file:sample.db?mode=rw', uri=True); connection.execute('CREATE INDEX idx_items_category_name ON items(category, name)'); connection.commit(); connection.close(); print('Created idx_items_category_name')"

What does this command do?

Python opens the existing sample.db file in read-write mode. The command creates idx_items_category_name with category as its first column.

The name column comes second in the index. This order matches the query's category filter before its name sort.

  • Confirm the terminal prints Created idx_items_category_name.

Index command did not finish?

  • Check that the second terminal tab is using the folder that contains sample.db.
  • Wait for any active Datasette request to finish before trying the command again.
  • Confirm the items table still uses the expected column names.

Help me diagnose why the SQLite index command failed.

That is the schema change complete. Datasette continues serving the same database while the new index gives SQLite another query plan to consider.

Compare the optimized trace

A fair performance comparison changes one factor at a time. You will run the identical query so the new index is the only deliberate difference.

  • Switch back to the Datasette SQL editor from the first substep.

Before you rerun the query, do you expect the database-related span to become shorter?

  • Run the unchanged query with the SQL editor's query control.
  • Confirm the result table still shows the same rows in the same name order.

The query result stays consistent. The optimization changes how SQLite reaches that result.

  • Switch back to the Jaeger browser tab from earlier.
  • Repeat the same trace search.
  • Open the newest Datasette request trace.
  • Expand the same type of database-related span that you measured before.
  • Record its duration as your post-index span duration.
  • Compare your post-index span duration against your pre-index span duration.

You should see a shorter database-related span in the post-index trace. That difference is the measured payoff from idx_items_category_name.

Post-index trace is not faster?

  • Confirm you opened the newest trace generated after the index command completed.
  • Compare the same database-related span in both traces.
  • Run the query one more time to reduce the effect of one unusually noisy request.

Help me compare my pre-index and post-index Jaeger traces.

Measurements become useful when you preserve the query plus the evidence plus the explanation. Your final note keeps that investigation reproducible.

  • Switch back to the second terminal tab from the index command.
  • Create slow-query-note.md beside sample.db by running this command:
python -c "note = '# Slow Query Investigation\n\n## Slowest query\n\nThe slowest query I found was:\n\n`SELECT id, category, name, description FROM items WHERE category = \'example-category\' ORDER BY name`\n\n## Trace comparison\n\n- Pre-index database span: [[PRE_INDEX_DURATION=\"your pre-index span duration\"]]\n- Post-index database span: [[POST_INDEX_DURATION=\"your post-index span duration\"]]\n\n## Finding\n\nThe original query was slow because `items(category, name)` was not indexed. Adding `idx_items_category_name` let SQLite use the category/name index for filtering and ordering.\n'; open('slow-query-note.md', 'w', encoding='utf-8').write(note)"

What does this command record?

The command writes the slow query plus both measured span durations to slow-query-note.md. It also records the missing category/name index as the original cause.

This connects the observed slowdown to the schema change that improved it. Your conclusion now has trace evidence behind it.

  • Check the note against the complete file below:

✔️ Awesome, I've got everything!

Your note contains the slow query plus both trace measurements plus the index explanation.

ⓧ I'd like to double check the full code

Here is the complete slow-query-note.md file at this point.

# Slow Query Investigation

## Slowest query

The slowest query I found was:

`SELECT id, category, name, description FROM items WHERE category = 'example-category' ORDER BY name`

## Trace comparison

- Pre-index database span: [[PRE_INDEX_DURATION="your pre-index span duration"]]
- Post-index database span: [[POST_INDEX_DURATION="your post-index span duration"]]

## Finding

The original query was slow because `items(category, name)` was not indexed. Adding `idx_items_category_name` let SQLite use the category/name index for filtering and ordering.

What should the note prove?

The note identifies the slowest query you tested. It connects the lower post-index duration to idx_items_category_name.

Before you print the note, do you expect to see both measured durations plus the explanation for the slowdown?

  • Print the saved note in your terminal by running this command:
python -c "print(open('slow-query-note.md', encoding='utf-8').read())"

What does this verification show?

The command reads the saved file from disk. Its output proves that slow-query-note.md contains the query plus the pre-index duration plus the post-index duration.

You should see the full investigation printed in the terminal. The post-index value should be lower than the pre-index value.

Note missing or incomplete?

  • Confirm the second terminal tab is still in the folder containing sample.db.
  • Run the note creation command again after checking both duration values.
  • Compare the printed content with the full-file reference above.

Help me fix my slow-query-note.md file.

You turned a slow request into a measured optimization. Your traces now show the before-and-after result while your note preserves the reason it improved.

Secret mission

Audit the Index Speedup

Turn your existing pre-index and post-index traces into a defensible performance verdict. Compare full-request speedup with database-span speedup to test whether the index changed the layer you expected.

Clean Up Your Resources

Clean Up Your Resources

Every resource in this project runs on your laptop with no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • The running Datasette 1.0a41 process serving sample.db with OpenTelemetry tracing.
  • The Jaeger all-in-one container running through Docker with its collected local traces.
  • The local SQLite database sample.db containing the items table plus the idx_items_category_name index.
  • The slow-query-note.md file recording your trace comparison.

Keep everything running

No action is needed. Choose this if you are still comparing query traces.

  • Leave the Datasette process running for more database experiments.
  • Leave the Jaeger container running to inspect your collected traces.
  • Keep sample.db plus slow-query-note.md for future comparisons.

Pause - I'll come back to this later

Shut down the running processes to free local memory. Your local artifacts stay on disk for your return.

  • Switch back to the terminal session running Datasette.
  • Press Ctrl+C to stop Datasette.
  • Confirm that your terminal prompt returns.
  • Use Docker's container list to find the Jaeger all-in-one container from earlier.
  • Stop the Jaeger all-in-one container.
  • Refresh http://localhost:16686 in your browser.

You now have a clean pause point. The Jaeger page should no longer load while your database plus note remain available.

Jaeger Still Loading?

Check Docker's container list for a Jaeger all-in-one container that is still running. Stop that container before refreshing the page again.

Ask for help if the container keeps running: Help me stop my local Jaeger all-in-one Docker container without deleting sample.db or slow-query-note.md.

Delete - I don't want to use this again

Stop every running process. Remove each local project artifact after the processes stop.

  • Switch back to the terminal session running Datasette.
  • Press Ctrl+C to stop Datasette.
  • Confirm that your terminal prompt returns.
  • Use Docker's container list to find the Jaeger all-in-one container from earlier.
  • Stop the Jaeger all-in-one container.
  • Delete the stopped Jaeger all-in-one container from the same container list.
  • Refresh http://localhost:16686 to confirm that the trace viewer stays unavailable.

The live services have stopped. Deleting the Jaeger container also removes the local traces it held.

macOS

  • Return to the terminal folder containing sample.db plus slow-query-note.md.
  • Remove the local project files with an immediate directory check by running:
rm -f sample.db slow-query-note.md
ls
What Do These Commands Do?

The rm command deletes both local files. The ls command lists every file that remains in the current folder.

You should no longer see sample.db or slow-query-note.md in the output. Your local project files are gone.

Still Seeing Either File?

Make sure the terminal is showing the folder that contains both files. Running the command from a different folder leaves the originals untouched.

Get help locating the files: Help me find and remove sample.db and slow-query-note.md from my local project folder on macOS.

Windows

  • Return to the PowerShell folder containing sample.db plus slow-query-note.md.
  • Remove the local project files with an immediate directory check by running:
Remove-Item -Path sample.db, slow-query-note.md
Get-ChildItem
What Do These Commands Do?

The Remove-Item command deletes both local files. The Get-ChildItem command lists every file that remains in the current folder.

You should no longer see sample.db or slow-query-note.md in the output. Your local project files are gone.

Still Seeing Either File?

Make sure PowerShell is showing the folder that contains both files. Running the command from a different folder leaves the originals untouched.

Get help locating the files: Help me find and remove sample.db and slow-query-note.md from my local project folder on Windows.

Nice Work!

Nice Work!

You did it! Your local Datasette 1.0a41 instance now sends OpenTelemetry traces from sample.db to Jaeger. You used those traces to prove that the idx_items_category_name index makes the target query faster.

What you learned:

  • Trace a Datasette request from the browser through its database-related spans in Jaeger.
  • Compare filtered and faceted requests using their request and database-related span durations.
  • Prove an index optimization with before-and-after traces for the same SQL query.
  • Secret Mission: Tackled an optional challenge to extend the trace-first investigation.

Ready to quiz yourself?