<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Anders Swanson on Medium]]></title>
        <description><![CDATA[Stories by Anders Swanson on Medium]]></description>
        <link>https://medium.com/@anders.swanson.93?source=rss-58a83f4309d6------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*-m72EhLCl0RU-eHzDuD6Ug.png</url>
            <title>Stories by Anders Swanson on Medium</title>
            <link>https://medium.com/@anders.swanson.93?source=rss-58a83f4309d6------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 18:03:49 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@anders.swanson.93/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[ORDS MCP with Keycloak IdP and Oracle AI Database Free]]></title>
            <link>https://medium.com/@anders.swanson.93/ords-mcp-with-keycloak-idp-and-oracle-ai-database-free-01d26592cf9c?source=rss-58a83f4309d6------2</link>
            <guid isPermaLink="false">https://medium.com/p/01d26592cf9c</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[mcp-server]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[rest-api]]></category>
            <category><![CDATA[software-engineering]]></category>
            <dc:creator><![CDATA[Anders Swanson]]></dc:creator>
            <pubDate>Thu, 12 Nov 2026 19:19:28 GMT</pubDate>
            <atom:updated>2026-11-12T19:19:28.797Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*PsQf-DuOTo3Oyy_8.png" /></figure><p>Ask your AI assistant a question about your data, and let it inspect the schema, write SQL, and run the query against Oracle AI Database.</p><p>In this walkthrough, we’ll connect Claude Code or Codex to <a href="https://andersswanson.dev/2025/05/22/oracle-database-for-free/">Oracle AI Database Free</a> through the Model Context Protocol (MCP) endpoint in Oracle REST Data Services (ORDS). Docker Compose starts the local lab, with Keycloak providing authentication.</p><p>By the end, you’ll be querying a sample dataset in plain English. <a href="https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/26.3/ordig/configuring-model-context-protocol-mcp.html">Oracle’s configuration guide</a> covers the endpoint and authentication settings used in the setup.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*Xfxkkz69_QzuZg0c" /></figure><h4>Download and start the sample</h4><p>Check out the <a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/ords-docker-compose/README.md">sample repository</a> on GitHub. You need Docker Compose, curl, jq, and a coding assistant like Claude Code or Codex.</p><p>Once you have the sample downloaded, change into the ords-docker-compose directory and start the containers using the mcp profile:</p><pre>cd ords-docker-compose<br>docker compose --profile mcp up -d --wait</pre><p>The profile starts Keycloak in development mode and imports the lab’s <a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/ords-docker-compose/keycloak/ords-lab-realm.json">realm configuration</a>. The <a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/ords-docker-compose/scripts/configure-mcp.sh">configure-mcp.sh script</a> is run during startup to set ORDS MCP settings to point to Keycloak, and to configure a separate database pool for MCP.</p><p>The database startup script, <a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/ords-docker-compose/oracle/ords_init.sql">ords_init.sql</a>, creates the sales schema, seeds the data, and creates the MCP_READER user. The mcp-config service waits for Keycloak and the initialized schema, then configures ORDS.</p><p>The sales schema has four related tables in TESTUSER and four read-only views with the same names in MCP_READER:</p><pre>Table / view  Rows  Contents<br>────────────  ────  ─────────────────────────────────<br>CUSTOMERS       6  Names, emails, cities, and<br>                   country codes<br>PRODUCTS        8  Product categories, current unit<br>                   prices, and stock quantities<br>ORDERS         12  Customer, order date, and status<br>ORDER_ITEMS    24  Order, product, quantity, and<br>                   purchase unit price</pre><p>Orders reference customers, and order items reference orders and products through foreign keys.</p><p>MCP_READER has CREATE SESSION and SELECT on the four base tables.</p><h4>Inspect a token from Keycloak</h4><p>Fetch a token from Keycloak using the helper script:</p><pre>export ORDS_MCP_TOKEN=$(./scripts/get-token.sh)</pre><p>This token can be used to authenticate against ORDS MCP, and contains the following metadata:</p><pre>Setting   Value<br>────────  ─────────────────────────────────────────<br>Realm     ords-lab<br>Issuer    http://localhost:8081/realms/ords-lab<br>Audience  http://localhost:8888/mcp<br>Scope     urn:oracle:dbtools:ords:mcpserver:all<br>Grant     client_credentials</pre><p>ORDS fetches public signing keys from http://keycloak:8080/realms/ords-lab/protocol/openid-connect/certs over the Compose network. The issuer is the public localhost URL. Keycloak’s <a href="https://www.keycloak.org/server/importExport">realm import</a> and <a href="https://www.keycloak.org/server/hostname">hostname configuration</a> documentation describe these mechanisms.</p><h3>Connect Codex desktop or CLI</h3><p>Add or update ords in ~/.codex/config.toml, replacing the token helper script path with the absolute path to your checkout:</p><pre>[mcp_servers.ords]<br>url = &quot;http://localhost:8888/mcp&quot;<br>http_headers_helper = &quot;/bin/bash /absolute/path/to/oracle-database-java-samples/ords-docker-compose/scripts/get-token.sh --headers&quot;</pre><p>The <a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/ords-docker-compose/scripts/get-token.sh">token script</a> returns a raw token by default or authorization-header JSON with --headers. Restart Codex once to load the configuration. See the <a href="https://learn.chatgpt.com/docs/extend/mcp?surface=desktop">official Codex MCP documentation</a> for additional details on configuring MCP.</p><h3>Connect Claude Code</h3><p>Merge this entry into .mcp.json in this lab directory, replacing the script path with the absolute path to your checkout:</p><pre>{<br>  &quot;mcpServers&quot;: {<br>    &quot;ords&quot;: {<br>      &quot;type&quot;: &quot;http&quot;,<br>      &quot;url&quot;: &quot;http://localhost:8888/mcp&quot;,<br>      &quot;headersHelper&quot;: &quot;/bin/bash /absolute/path/to/oracle-database-java-samples/ords-docker-compose/scripts/get-token.sh --headers&quot;<br>    }<br>  }<br>}</pre><p>The same helper supplies fresh authorization headers at connection time. After a tool call returns HTTP 401 or 403, Claude Code re-runs the helper, reconnects, and retries once.</p><p>See the <a href="https://code.claude.com/docs/en/mcp#use-dynamic-headers-for-custom-authentication">official Claude Code dynamic-header documentation</a>.</p><h3>Try a query</h3><p>In either client, ask:</p><blockquote><em>Use the ORDS MCP server to discover the mcp-lab database, inspect its schema, and report the total order value by status.</em></blockquote><p>The sample returns CANCELLED = 99.93, DELIVERED = 709.82, PENDING = 821.78, and SHIPPED = 584.38. Try some other prompts to experiment:</p><ul><li>Which customers have the highest order value, excluding cancelled orders?</li><li>What is the order value by product category and customer country?</li><li>How does October order value compare with November, excluding cancelled orders?</li><li>Which products appear in pending orders, and how much stock is available?</li></ul><p>For example, the category and country question can use this SQL through sql_run:</p><pre>select c.country, p.category, sum(i.quantity * i.unit_price) as total<br>from customers c<br>join orders o on o.customer_id = c.id<br>join order_items i on i.order_id = o.id<br>join products p on p.id = i.product_id<br>where o.status &lt;&gt; &#39;CANCELLED&#39;<br>group by c.country, p.category<br>order by c.country, p.category;</pre><p>Both Codex and Claude Code renew expired tokens through the helper above. See Oracle’s <a href="https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/26.3/ordig/configuring-model-context-protocol-mcp.html">MCP configuration guide</a> and <a href="https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/26.3/orddg/using-ords-model-context-protocol-mcp.html">MCP developer guide</a> for the authentication and tool interfaces.</p><h4>Customization</h4><ul><li>Add database initialization scripts to oracle/ (e.g., modify ords_init.sql or add your own scripts).</li><li>Environment variables can be adjusted in docker-compose.yml (e.g., change passwords or ports).</li></ul><h4>Notes</h4><ul><li>The database has no persistent data volume in this sample; recreating ordsdb discards its data. Back up any work before switching images.</li><li>ORDS uses container-registry.oracle.com/database/ords:latest.</li><li>Volumes persist configuration and downloaded APEX files; remove them with docker-compose down -v if needed.</li><li>If you first started the database without APEX and later enable APEX, use docker-compose down -v before restarting so the Oracle AI Database container initializes cleanly with APEX.</li><li>For more details on ORDS, visit the <a href="https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/">official documentation</a>.</li></ul><p>If you encounter issues, check container logs:</p><pre>docker logs ords<br>docker logs ordsdb</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=01d26592cf9c" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ORDS Observability with OpenTelemetry]]></title>
            <link>https://medium.com/@anders.swanson.93/ords-observability-with-opentelemetry-b64020efa690?source=rss-58a83f4309d6------2</link>
            <guid isPermaLink="false">https://medium.com/p/b64020efa690</guid>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[observability]]></category>
            <category><![CDATA[rest-api]]></category>
            <dc:creator><![CDATA[Anders Swanson]]></dc:creator>
            <pubDate>Tue, 10 Nov 2026 16:23:30 GMT</pubDate>
            <atom:updated>2026-11-10T16:23:30.343Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*skdDeEa5WTJwPUqK.png" /></figure><p>Oracle REST Data Services (ORDS) is a great addition to your Oracle AI Database stack. But, do you know how to configure ORDS observability?</p><p>In this article, we’ll demonstrate how to collect telemetry data from ORDS that shows what happens during HTTP request processing, which SQL operations ran, what ORDS logged, and the state of the ORDS connection pool.</p><p>This lab builds on the <a href="https://andersswanson.dev/2026/11/04/stop-guessing-full-stack-oracle-observability/">Full-Stack Oracle Observability Lab</a>, using OpenTelemetry to collect observability data from ORDS with <a href="https://andersswanson.dev/2025/05/22/oracle-database-for-free/">Oracle AI Database Free</a>. <a href="https://github.com/anders-swanson/oracle-database-code-samples/tree/main/oracledb-e2e-observability"><strong>You can check out the code on GitHub;</strong></a> if you like the lab, give the repo a star!</p><h4>ORDS Observability with OpenTelemetry and Grafana</h4><p>In the lab, ORDS runs as a container with the OpenTelemetry Java Agent attached. The agent sends telemetry to the OpenTelemetry Collector inside Grafana LGTM. Tempo stores traces, Loki stores logs, and Prometheus stores metrics.</p><p>Installing the OpenTelemetry agent and configuring ORDS in the <a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/oracledb-e2e-observability/ords/Dockerfile">Dockerfile</a>. Always use the latest agent version:</p><pre>FROM container-registry.oracle.com/database/ords:latest<br># Pin the agent and verify the release artifact at build time.<br>RUN curl -fsSL https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v2.33.1/opentelemetry-javaagent.jar \<br>        -o /opt/oracle/ords/opentelemetry-javaagent.jar \<br>    &amp;&amp; echo &#39;e43a270df53c6f423e0ab157be9c27ccc01d5dc3232e162fe54777573db2f149  /opt/oracle/ords/opentelemetry-javaagent.jar&#39; | sha256sum -c -<br># Keep installation and SQLcl commands free of instrumentation.<br>RUN mv /opt/oracle/ords/bin/ords /opt/oracle/ords/bin/ords-original<br>COPY --chown=oracle:oinstall serve-with-agent.sh /opt/oracle/ords/bin/ords<br>RUN chmod +x /opt/oracle/ords/bin/ords<br>COPY setup-schema.sh /ords-entrypoint.d/10-setup-schema.sh<br>COPY schema.sql /opt/oracle/lab-schema.sql</pre><p>The lab attaches the Java agent when starting ords serve, following the approach described in the <a href="https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/26.3/ordig/deploying-and-monitoring-oracle-rest-data-services.html">ORDS OpenTelemetry documentation</a>.</p><p>Agent attachment uses the serve-with-agent.sh script:</p><pre>if [[ &quot;${1:-}&quot; == &quot;serve&quot; ]]; then<br>    set -- --java-options &quot;-javaagent:/opt/oracle/ords/opentelemetry-javaagent.jar&quot; &quot;$@&quot;<br>fi<br>exec /opt/oracle/ords/bin/ords-original &quot;$@&quot;</pre><p>The environment variables configured in the lab look like this:</p><pre>OTEL_SERVICE_NAME: ords<br>OTEL_EXPORTER_OTLP_ENDPOINT: http://grafana-lgtm:4318<br>OTEL_EXPORTER_OTLP_PROTOCOL: http/protobuf<br>OTEL_TRACES_EXPORTER: otlp<br>OTEL_METRICS_EXPORTER: otlp<br>OTEL_LOGS_EXPORTER: otlp<br>OTEL_METRIC_EXPORT_INTERVAL: &quot;15000&quot;<br>OTEL_TRACES_SAMPLER: always_on<br>OTEL_INSTRUMENTATION_JAVA_UTIL_LOGGING_ENABLED: &quot;true&quot;</pre><p>The service name identifies ORDS across the telemetry signals. The collector endpoint uses its Compose service address because ORDS runs inside the container network.</p><p>The Oracle AI Database Metrics Exporter runs separately, adding complementary database metrics.</p><h4>Start the lab with ORDS enabled</h4><p>You’ll need Docker with Compose v2 and at least 8 GB of memory allocated to Docker. From the lab directory, enable the ords profile with Docker Compose:</p><pre>docker compose --profile ords up \<br>  -d --build --wait --wait-timeout 900</pre><p>This starts ORDS alongside the database and observability services. On its first start, ORDS installs its metadata and enables the lab’s REST endpoint.</p><h4>Follow a request through ORDS</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*KCVYCDaA-wp8z-uA" /></figure><p>First, generate some continuous traffic with the <a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/oracledb-e2e-observability/workload.sh">workload.sh script</a>:</p><pre>ORDS_URL=http://localhost:8888/ords/lab/readings/ ./workload.sh</pre><p>Metrics export every 15 seconds; allow about a minute of traffic for rate and p95 panels to populate.</p><p>Open <a href="http://localhost:3000/">Grafana</a> and select <strong>Dashboards → Oracle AI Database → ORDS observability</strong>.</p><p>Use the <strong>ORDS traces</strong> link, or select Tempo in Explore and search:</p><pre>{ resource.service.name = &quot;ords&quot; }</pre><p>Open a request to /ords/lab/readings/. Its trace connects the HTTP request with operations beneath it, including connection acquisition, dispatch, and JDBC work against LAB_READINGS_REST.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*VWG9XtKXR68fV6QJ" /></figure><p>The ORDS dashboard brings together HTTP request rate and p95 latency, UCP connection usage and pending requests, JVM memory and CPU, and runtime logs.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*0vOtrBVLk552xmkr" /></figure><p>ORDS metrics come from the Java agent. Database metrics come from the <a href="https://github.com/oracle/oracle-db-appdev-monitoring"><strong>Oracle AI Database Metrics Exporter</strong></a>.</p><p>When you’re done with the lab, stop the workload and shut down the containers:</p><pre>docker compose --profile ords down</pre><h4>References</h4><ul><li><a href="https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/26.3/ordig/deploying-and-monitoring-oracle-rest-data-services.html">ORDS deployment, monitoring, and OpenTelemetry integration</a>.</li><li><a href="https://explorer.opentelemetry.io/java-agent/instrumentation/java-util-logging">OpenTelemetry Java Util Logging instrumentation</a>.</li><li><a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/oracledb-e2e-observability/README.md">Lab README</a>.</li><li><a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/oracledb-e2e-observability/docker-compose.yml">Compose configuration</a>.</li><li><a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/oracledb-e2e-observability/ords/schema.sql">REST schema and read-only view</a>.</li><li><a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/oracledb-e2e-observability/grafana/dashboards/ords.json">ORDS Grafana dashboard</a>.</li><li><a href="https://oracle.github.io/oracle-db-appdev-monitoring/">Oracle AI Database Metrics Exporter documentation</a>.</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b64020efa690" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Stop Guessing: Full-Stack Oracle Observability]]></title>
            <link>https://medium.com/@anders.swanson.93/stop-guessing-full-stack-oracle-observability-62adca6a4cbf?source=rss-58a83f4309d6------2</link>
            <guid isPermaLink="false">https://medium.com/p/62adca6a4cbf</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[observability]]></category>
            <category><![CDATA[oracle]]></category>
            <dc:creator><![CDATA[Anders Swanson]]></dc:creator>
            <pubDate>Wed, 04 Nov 2026 19:35:34 GMT</pubDate>
            <atom:updated>2026-11-04T19:35:34.065Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*aCFfjHMvTOaFBAH4.png" /></figure><p>A production request slows down. Was it waiting for a connection or executing SQL? Was the database busy, and which logs belong to that request?</p><p><strong>Open-source observability tools</strong> help connect the evidence across the application and Oracle AI Database. Trace spans capture operation timing, attributes, and errors. Correlated logs add request-specific details, while metrics show how latency, errors, and resource usage changed over time.</p><p>We’ll explore these signals with a <a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/oracledb-e2e-observability/README.md">simple Spring Boot application</a> that queries <a href="https://andersswanson.dev/2025/05/22/oracle-database-for-free/">Oracle AI Database Free</a> through Universal Connection Pool (UCP) and exports telemetry to Grafana LGTM. We’ll follow a request through its JDBC calls and matching logs, then examine pool and database metrics to see what was happening around it.</p><h4>End-to-end tracing, metrics, and logs</h4><p>The sample Spring Boot app connects to Oracle AI Database Free, returns its trace ID, and sends telemetry to Grafana LGTM. A separate metrics exporter adds database context. <strong>OpenTelemetry</strong> is used to push data into Grafana LGTM: Tempo stores traces, Loki stores logs, and Prometheus stores metrics, with Grafana providing the interface for exploring them.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*SrchzAGTzLIAKY0o" /></figure><p>The lab uses Docker Compose for Oracle AI Database Free, Grafana LGTM, the metrics exporter, and an NGINX HTTPS proxy. The Spring Boot application runs on your host through Maven.</p><p>From the sample directory, start the infrastructure:</p><pre>docker compose up -d --wait --wait-timeout 900</pre><p>Then start the application in another terminal:</p><pre>mvn spring-boot:run</pre><p>Call the endpoint:</p><pre>curl --fail \<br>  -H &#39;X-Client-Id: blog-demo&#39; \<br>  http://localhost:8081/api/readings</pre><p>For continuous traffic, run:</p><pre>./workload.sh</pre><p>The response contains a trace ID and session information observed by the database: module, action, client identifier, and session ID. Use the trace ID to find the request in Tempo and the session fields to identify its database context.</p><p>Open <a href="http://localhost:3000/">Grafana</a> and select <strong>Dashboards → Oracle AI Database → Oracle AI Database end-to-end observability</strong>. Allow about a minute of traffic for rate and percentile panels to populate.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Cd-mm50TUTva_4j4" /></figure><p>In Tempo, paste trace IDs to view end-to-end spans on database work: from the HTTP request to the database server.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*cCE8EkiT9dwEDug0" /></figure><p>From the trace in Tempo, follow the link to Loki and find the application log with the same trace ID. The “Read 2 readings…” message belongs to this request, so you can inspect its application context without searching through unrelated messages.</p><h4>Diagnostic context for database apps</h4><p>Traces describe a single request, metrics help us understand the state of the system around that request.</p><p>The <a href="https://github.com/oracle/oracle-db-appdev-monitoring"><strong>Oracle AI Database Metrics Exporter</strong></a> provides database metrics. In the lab, it collects database metrics every 15 seconds and pushes them to Grafana LGTM over OTLP/gRPC. The dashboard brings database sessions and PDB tablespace utilization alongside application latency, errors, and UCP connection-pool metrics.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*oE9Avhp_tUZVKFHz" /></figure><p>If a request is slow, inspect its JDBC spans, then check connection-pool acquisition time, pending requests, and database metrics over the same window. Those trends can reveal pressure that helps explain the request’s timing.</p><p>The exporter includes default database metrics and supports custom metrics defined with SQL queries in YAML or TOML. It can expose metrics for Prometheus collection, push them through OTLP/gRPC, or support both paths.</p><p>See the <a href="https://oracle.github.io/oracle-db-appdev-monitoring/"><strong>exporter documentation</strong></a> for setup and customization, and the <a href="https://oracle.github.io/oracle-db-appdev-monitoring/docs/next/configuration/otlp/"><strong>OTLP publishing guide</strong></a> for configuration details.</p><h4>Putting it all together</h4><p>Observability brings the application and database into the same investigation. You can follow a request through its JDBC calls and database execution, read the logs that belong to it, and check what the connection pool and database were doing at the time. Together, those details help explain where the request spent its time and what might have affected it.</p><p>A forthcoming post will explore ORDS metrics, logs, and traces using the same stack. If you want to try it now, enable the optional ords profile in the lab.</p><h4>References</h4><ul><li><strong>Oracle AI Database Metrics Exporter:</strong> <a href="https://github.com/oracle/oracle-db-appdev-monitoring">GitHub repository</a> and <a href="https://oracle.github.io/oracle-db-appdev-monitoring/">documentation</a>.</li><li><strong>Database server-side tracing:</strong> <a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/tgsql/observabilitly-open-telemetry.html">Database Observability and OpenTelemetry</a> and the <a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/arpls/dbms_observability.html">DBMS_OBSERVABILITY reference</a>.</li><li><strong>UCP metrics:</strong> <a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/jjucp/pool-statistics.html">Connection-pool runtime statistics</a>.</li><li><strong>UCP Spring Boot integration:</strong> <a href="https://oracle.github.io/spring-cloud-oracle/site/docs/database/ucp/">Data source configuration</a> and <a href="https://oracle.github.io/spring-cloud-oracle/site/docs/database/ucp/#micrometer-metrics">Micrometer metrics</a>.</li></ul><p>Lab source code</p><ul><li><a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/oracledb-e2e-observability/README.md">Lab README</a> — setup, walkthrough, optional ORDS profile, and troubleshooting.</li><li><a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/oracledb-e2e-observability/docker-compose.yml">Docker Compose stack</a> — infrastructure services and their connections.</li><li><a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/oracledb-e2e-observability/src/main/resources/application.yaml">Application configuration</a> — UCP, JDBC observability, and telemetry export.</li><li><a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/oracledb-e2e-observability/src/main/java/com/example/observability/ReadingsController.java">Readings controller</a> — SQL execution, request observation, and correlated logging.</li><li><a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/oracledb-e2e-observability/src/main/java/com/example/observability/ClientInfoDataSource.java">Connection decorator</a> — session identity and JDBC server-telemetry settings.</li><li><a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/oracledb-e2e-observability/oracle/observability.sql">Database observability setup</a> — native tracing service and export endpoint.</li><li><a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/oracledb-e2e-observability/exporter/config.yaml">Metrics exporter configuration</a> — database connection, collection interval, and OTLP destination.</li><li><a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/oracledb-e2e-observability/grafana/dashboards/observability.json">Grafana dashboard</a> — application, connection-pool, and database panels.</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=62adca6a4cbf" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Oracle Kafka APIs and OpenTelemetry]]></title>
            <link>https://medium.com/@anders.swanson.93/oracle-kafka-apis-and-opentelemetry-fd4a8bcf7351?source=rss-58a83f4309d6------2</link>
            <guid isPermaLink="false">https://medium.com/p/fd4a8bcf7351</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[kafka]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[observability]]></category>
            <dc:creator><![CDATA[Anders Swanson]]></dc:creator>
            <pubDate>Thu, 08 Oct 2026 22:39:52 GMT</pubDate>
            <atom:updated>2026-10-08T22:39:52.463Z</atom:updated>
            <content:encoded><![CDATA[<h3>Combine events, transactions, and relational data, then follow metrics, logs, and traces</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*wWcEKPBgEKdYDHYL.png" /></figure><p>Publish a message, save it in a relational table, and follow the trace from producer to consumer with Oracle Kafka APIs. In this article, we’ll combine <strong>events, transactions, relational data, and OpenTelemetry metrics, logs, and traces</strong> with a small Java app and Oracle AI Database Free.</p><p>You can run the entire lab on your laptop, provided you have a docker-compatible environment. <a href="https://www.oracle.com/database/free/">Oracle AI Database Free</a> is available at no cost under Oracle’s free use terms.</p><p>If you’d like to skip the article and jump to the lab, <a href="https://github.com/anders-swanson/oracle-database-code-samples/tree/main/oracle-database-kafka-apis#okafka-metrics-tracing-and-logs-with-spring-boot-actuator-and-opentelemetry"><strong>download the lab from GitHub</strong></a>, and give it a star if it was useful!</p><h3>Oracle Kafka APIs</h3><p><a href="https://andersswanson.dev/2025/07/09/authenticate-to-your-oracle-database-like-its-a-kafka-cluster/">Oracle’s Kafka Java client, OKafka</a>, uses familiar kafka-clients APIs to create topics, produce events, and consume events. Under the hood, it’s backed by <a href="https://andersswanson.dev/2025/09/18/pub-sub-in-your-db-oracle-database-txeventq/"><strong>Oracle AI Database Transactional Event Queues</strong></a> (TxEventQ), a high-throughput messaging system <strong>built into the database</strong>.</p><p>The <em>really cool</em> part about TxEventQ is the ability to combine messaging (pub/sub of events) with database transactions. E.g., a producer publishes an event and inserts a SQL row using the same database connection; <strong>both commit together.</strong></p><p>Why is this useful? When events describe business data,<strong> failures must not </strong>leave events published while the corresponding SQL change is missing (or vice versa, and corollary to consumers).</p><h3>Adding Observability with OpenTelemetry</h3><p>If you’ve ever run a messaging system in production, you know it needs telemetry. We can monitor the database server side easily with the <a href="https://github.com/oracle/oracle-db-appdev-monitoring">Oracle AI Database Metrics Exporter</a> (which includes a TxEventQ addon), but what about the client side?</p><p>OpenTelemetry gives us three ways to see how data flows in the client:</p><p>SignalWhat it tells us<strong>Metrics</strong>How the clients behave over time: transaction timings, consumer throughput, and more.<strong>Logs</strong>What happened: a reading was published, processed, or included in a batch that committed or rolled back.<strong>Traces</strong>How a reading moves from producer to consumer, with related spans and correlated logs.</p><p>In the lab, Spring Boot and Micrometer export all three through the <strong>OpenTelemetry Protocol</strong> (OTLP). Sender and receiver observations let Spring’s tracing handlers propagate context through <strong>Kafka record headers</strong>.</p><p>That said, let’s get started with the lab.</p><h4>1. Download the sample</h4><p>To run the sample, you’ll need Java 21, Maven, Git, and a running Docker-compatible environment with Docker Compose.</p><p><a href="https://github.com/anders-swanson/oracle-database-code-samples/tree/main/oracle-database-kafka-apis">Download the sample from GitHub</a>. The <a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/oracle-database-kafka-apis/README.md">README</a> contains the configuration reference and additional tests.</p><pre>git clone https://github.com/anders-swanson/oracle-database-java-samples.git<br>cd oracle-database-java-samples/oracle-database-kafka-apis</pre><h4>2. Start Oracle AI Database Free and Grafana LGTM</h4><pre>docker compose up -d --wait</pre><p>Compose starts an <strong>Oracle AI Database Free</strong> container and Grafana’s LGTM stack, which includes an OpenTelemetry Collector and backends for metrics, logs, and traces.</p><p>During startup, a SQL script is automatically run to create the lab user, apply grants, and create tables. First-time startup will include image downloads.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/970/0*nWqCXEYMWK-N4Rkj" /></figure><h3>3. Run the Java application</h3><p>From the same directory, run</p><pre>mvn spring-boot:run</pre><p>The application publishes an event every 500 milliseconds and continuously consumes events. Regular readings are inserted into the table; consumption updates their consumed_at timestamps. Occasionally, the producer publishes a failure marker. The consumer rolls back a batch containing that marker once, then processes it again and commits.</p><p>Watch for Published, Processed, Rolled back, and Committed messages. A Processed message describes work before the batch commits; the SQL table shows committed results.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1023/0*ezVQZEwcWvENLIRp" /></figure><h4>4. Explore metrics, logs, and traces</h4><p>Open <a href="http://localhost:3000/"><strong>Grafana</strong></a> to view the <a href="http://localhost:3000/d/okafka-metrics-sample">OKafka Metrics Sample dashboard</a>, which shows producer transaction timings, consumer throughput, polling activity, JVM memory, and CPU usage.</p><p>Allow about a minute for panels to fully populate with metric data.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*a7qoBv3gA2SA4s3Z" /></figure><p>Traces can be viewed in the Traces Drilldown. Each traces should propagate from the producer to consumer. You may also view logs in the Logs Drilldown.</p><p>You can also check Spring Boot Actuator for health and metrics data:</p><pre>curl http://localhost:8080/actuator/health<br>curl http://localhost:8080/actuator/metrics</pre><h4>5. Stop and clean up</h4><p>When you’re done, stop the Java process with<strong> Ctrl+C</strong>. Remove the lab containers with docker compose:</p><pre>docker compose down</pre><p><strong>Articles and lab overview</strong></p><ul><li><a href="https://www.oracle.com/database/free/">Oracle AI Database Free</a></li><li><a href="https://github.com/anders-swanson/oracle-database-code-samples/tree/main/oracle-database-kafka-apis">Lab source code</a></li><li><a href="https://github.com/anders-swanson/oracle-database-code-samples/tree/main/oracle-database-kafka-apis#okafka-metrics-tracing-and-logs-with-spring-boot-actuator-and-opentelemetry">Lab instructions: OKafka metrics, tracing, and logs</a></li><li><a href="https://youtu.be/5Meib9LLQRI">Lab video</a></li><li><a href="https://andersswanson.dev/2025/07/09/authenticate-to-your-oracle-database-like-its-a-kafka-cluster/">Authenticate to Oracle AI Database Like It’s a Kafka Cluster</a></li><li><a href="https://andersswanson.dev/2025/09/18/pub-sub-in-your-db-oracle-database-txeventq/">Pub/Sub in Your DB: Oracle AI Database TxEventQ</a></li><li><a href="https://github.com/oracle/oracle-db-appdev-monitoring">Oracle AI Database Metrics Exporter</a></li></ul><p><strong>Relevant lab files</strong></p><ul><li><a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/oracle-database-kafka-apis/src/main/java/com/example/metrics/MetricsSample.java">MetricsSample.java — transactional producer, consumer, and trace propagation</a></li><li><a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/oracle-database-kafka-apis/src/main/java/com/example/metrics/MetricsApplication.java">MetricsApplication.java — application startup, metric binders, and log export wiring</a></li><li><a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/oracle-database-kafka-apis/src/main/java/com/example/metrics/MetricsClientConfiguration.java">MetricsClientConfiguration.java — OKafka producer and consumer configuration</a></li><li><a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/oracle-database-kafka-apis/src/main/resources/application.yaml">application.yaml — telemetry endpoints and trace sampling</a></li><li><a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/oracle-database-kafka-apis/docker-compose.yml">docker-compose.yml — Oracle AI Database Free and Grafana LGTM</a></li><li><a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/oracle-database-kafka-apis/docker/init.sql">init.sql — database user and startup configuration</a></li><li><a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/oracle-database-kafka-apis/docker/grafana/dashboards/okafka-metrics.json">Grafana dashboard JSON</a></li><li><a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/oracle-database-kafka-apis/pom.xml">pom.xml — lab dependencies and build configuration</a></li></ul><p><strong>Local endpoints — available while the lab is running</strong></p><ul><li><a href="http://localhost:3000/">Grafana</a></li><li><a href="http://localhost:3000/d/okafka-metrics-sample">OKafka Metrics Sample dashboard</a></li><li><a href="http://localhost:8080/actuator/health">Actuator health</a></li><li><a href="http://localhost:8080/actuator/metrics">Actuator metrics</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fd4a8bcf7351" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Event-driven memory for AI agents with Oracle OKafka]]></title>
            <link>https://medium.com/@anders.swanson.93/event-driven-memory-for-ai-agents-with-oracle-okafka-a057f0fbe78a?source=rss-58a83f4309d6------2</link>
            <guid isPermaLink="false">https://medium.com/p/a057f0fbe78a</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[software-engineering]]></category>
            <dc:creator><![CDATA[Anders Swanson]]></dc:creator>
            <pubDate>Tue, 06 Oct 2026 15:19:09 GMT</pubDate>
            <atom:updated>2026-10-06T15:19:09.965Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*J8rLUFi3LrqXT3oK.png" /></figure><p>When an agent finishes a task, the next session usually starts from zero. A transcript may exist somewhere, but no useful handoffs, preferences, or decisions make it into persistent, searchable memory.</p><p>A transcript records what happened. Durable memory is a smaller, curated set of information selected for a purpose, with a source, an access scope, and rules for correction and deletion. For example, a user-approved decision is likely to be useful in future sessions, while an incidental tool result or sensitive detail probably isn’t.</p><p>Memory curation and selection can take time. For agents using Oracle AI Database, <a href="https://andersswanson.dev/2025/07/09/authenticate-to-your-oracle-database-like-its-a-kafka-cluster/">OKafka</a> and <a href="https://andersswanson.dev/2025/09/18/pub-sub-in-your-db-oracle-database-txeventq/">Transactional Event Queues (TxEventQ)</a> enable asynchronous processing of memory candidates. A worker can extract memory candidates, apply filters and rules, and then store memories for retrieval. The invoking agent is able to respond without waiting for the asynchronous work to finish.</p><p>Just looking for code? <a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/okafka-agent-memory/README.md"><strong>Check out companion lab on GitHub.</strong></a></p><h3>Context, transcripts, and memory are distinct</h3><p>Agents have a few, general types of state, <strong>working context, transcript history, and durable memory</strong>.</p><p>Three kinds of agent stateCurrent conversation, recorded history, selected knowledge.Working contextBounded chat windowHelps this conversationTranscript historyUser, agent, and toolsAudit and reviewDurable memorySelected factsDecisions and handoffsReusable later</p><p>Transcripts are evidence of what the agent did: promoting transcripts to durable memory requires curation, ownership, retention, and governance rules.</p><p>For example, append-only conversation logs can be kept separate from curated JSON document memory and associated embeddings. An agent can query durable memory with vector similarity and full-text search, while its write-back tool stores handoffs/decisions as requested.</p><h3>Add an event path for memory work</h3><p>Asynchronous, event-driven design moves expensive work away from the main agent. An agent can finish its work while an asynchronous event consumer handles long-running work in the background, complete with retries, idempotency, and callbacks.</p><p>A possible event-driven memory flow looks like this:</p><ol><li><strong>Capture an agent interaction: </strong>Create an event for a distinct unit of work, including identifiers like conversation ID, event ID, source transcript, timestamp, and other metadata. Apply access-control or redaction rules before publish.</li><li><strong>Publish the event</strong>: A producer (OKafka) writes the event to a TxEventQ topic, alongside related database operations in the same transaction.</li><li><strong>Process/extract memory candidates asynchronously: </strong>A consumer group reads agent events and applies filtering, extraction, deduplication, and conflict handling. LLMs and other controls can help identify facts, coupled with deterministic scope and policy validation. Depending on the type of processing you use, this step can use one or more consumers, with distributed routing and retries/idempotency.</li><li><strong>Store memory:</strong> Save approved memory documents with their metadata, including embedding and search index updates.</li><li><strong>Retrieve during a later conversation:</strong> Search the memory store using semantic and exact-match signals, apply authorization and scope filters, and return the supporting memory references to the agent.</li></ol><p>From event to agent memory01Captureevent + IDpolicy02PublishOKafkaTxEventQ03Curateasync extractvalidate04Storeembed + indexprovenance05Retrievescoped searchreferences</p><p>Oracle OKafka allows the event stream and memory data to live in the same database. Each processing stage can commit event consumption, related database writes, and publish of the next event together on the consumer’s database connection. Failed transactions roll everything back back together.</p><h4>Async fan-out for sub-agents</h4><p>Event streaming decouples the agent from post-processing: sub-agents can subscribe and process the data while the main agent responds to the caller.</p><p>An initial agent can create an artifact (like a transcript record), publish that event to a TxEventQ topic in the database, and have one or more sub-agents asynchronously process the record. For example, sub-agent can be used to asynchronously extract candidates, deduplicate memories, and produce vector embeddings.</p><p>Fan out to three sub-agentsSeparate consumer groups, separate tasks.OKafkaProducerTxEventQ topicGroup AAgent: analyzeGroup BAgent: enrichGroup CAgent: notify</p><h3>Reference Implementations</h3><h4>OKafka Agent Memory</h4><p>The <a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/okafka-agent-memory/README.md">companion lab</a> implements a simple agent memory flow with Oracle AI Database, OKafka, and OCI Generative AI.</p><p>From transcripts to memoryOnly selected facts become reusable memory.TranscriptCaptureCurateExtract · judgeMemoryEmbed · searchOKafka + TxEventQ · Oracle AI Databaseokafka-agent-memory lab</p><p>Three event consumers handle transcript intake, memory extraction and admission, and embedding generation. Source transcripts remain separate from selected memories, which can be retrieved through an owner-scoped search API. An accepted transcript can finish without producing any memory.</p><h4>OKafka Basics</h4><p>These OKafka code samples cover the building blocks of this architecture, providing building blocks for memory processing:</p><ul><li>The <a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/migrate-kafka-to-oracle/README.md">migrate-kafka-to-oracle walkthrough</a> starts with a plain Kafka Java producer and consumer, then switches to OKafka and TxEventQ. Later steps add OSON serialization and database work in the consumer transaction.</li><li>The <a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/support-ticket-intelligence/README.md">support-ticket-intelligence sample</a> demonstrates an event-driven enrichment path. It inserts a ticket and publishes a TicketOpened event atomically; a consumer then creates searchable chunks and embeddings. The same event-and-consumer structure could trigger memory extraction after an agent interaction.</li><li>The <a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/txeventq-fraud-detection/README.md">txeventq-fraud-detection sample</a> shows transactional production of OSON events and a consumer that uses its database connection while scoring and persisting decisions.</li><li>The <a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/oracle-database-kafka-apis/README.md">oracle-database-kafka-apis integration tests</a> exercise topic creation, production, consumption, and transactional producer and consumer behavior with Oracle AI Database Free and Testcontainers.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*J2GCvq0N6U25BPjF" /></figure><p>If you like the samples, <a href="https://github.com/anders-swanson/oracle-database-code-samples">star the repository</a>!</p><h3>When does event-streaming fit agent workflows?</h3><p>Event-streaming fits agent workloads when follow-up work is required on the generated transcripts, especially when this work is time consuming and requires stateful, durable execution.</p><p>For event-driven agent workflows, TxEventQ provides durable streaming: Oracle AI Database persists transcript data, curated memory documents, metadata, and vectors. OKafka gives familiar Java APIs to your agents to publish and consume events in the database, using database transactions.</p><p>Async work also means consistency tradeoffs: a background work might finish its work after the next agent begins a new task.</p><h3>Further reading</h3><p>Multi-model database and OKafka</p><ul><li><a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/okafka-agent-memory/README.md"><strong>Companion Lab: OKafka Agent Memory</strong></a></li><li><a href="https://andersswanson.dev/2026/05/19/an-app-that-keeps-its-data-in-one-place-multi-model-database-pattern/">An app that keeps its data in one place: multi-model database pattern</a></li><li><a href="https://andersswanson.dev/2026/08/03/no-separate-database-required-real-time-multi-model-data-processing/">No separate database required: real-time, multi-model data processing</a></li><li><a href="https://andersswanson.dev/2025/05/28/migrate-apache-kafka-applications-to-oracle-database/">Migrate Apache Kafka applications to Oracle AI Database: Part I</a></li><li><a href="https://andersswanson.dev/2025/09/18/pub-sub-in-your-db-oracle-database-txeventq/">Pub/Sub in your DB? Oracle AI Database TxEventQ</a></li><li><a href="https://github.com/oracle/okafka">Oracle’s OKafka project</a></li></ul><p>Event streaming for agents</p><ul><li><a href="https://atlan.com/know/event-driven-architecture-for-ai-agents/">Event-Driven Architecture for AI Agents: Patterns and Benefits — Atlan</a></li><li><a href="https://andersswanson.dev/2026/07/07/building-agent-memory-why-your-agents-need-real-memory/">Oracle AI Database for agent memory</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a057f0fbe78a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[True Cache with Docker Compose and redis-cli]]></title>
            <link>https://medium.com/@anders.swanson.93/true-cache-with-docker-compose-and-redis-cli-35de047f4a4c?source=rss-58a83f4309d6------2</link>
            <guid isPermaLink="false">https://medium.com/p/35de047f4a4c</guid>
            <category><![CDATA[software-testing]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[database]]></category>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[software-engineering]]></category>
            <dc:creator><![CDATA[Anders Swanson]]></dc:creator>
            <pubDate>Wed, 30 Sep 2026 16:15:02 GMT</pubDate>
            <atom:updated>2026-09-30T16:15:02.133Z</atom:updated>
            <content:encoded><![CDATA[<h3>Run Oracle True Cache locally, watch inventory changes reach the cache, and read the result with redis-cli.</h3><p>This article demonstrates how to run Oracle True Cache locally with docker-compose for testing, local development, and experimentation. We’ll also enable the <a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/odbtc/using-oracle-true-cache-redis-server.html">True Cache Redis API</a> and verify it with redis-cli.</p><p>In the compose script, one container serves as the primary database, while the second is the True Cache read-only replica. Both the primary and True Cache databases use the <a href="https://andersswanson.dev/2025/05/22/oracle-database-for-free/">container-registry.oracle.com/database/free:latest</a> image.</p><p>If you’d like to skip the article and jump to the code sample, <a href="https://github.com/anders-swanson/oracle-database-code-samples/tree/main/true-cache-docker-compose"><strong>Start Here</strong></a>. If you like the sample, <a href="https://github.com/anders-swanson/oracle-database-code-samples">give the repo a star</a>!</p><h3>Prerequisites</h3><ul><li>Docker and Docker Compose.</li><li><strong>Optional</strong>: redis-cli to run the separate Redis API test.</li></ul><h3>Background: Oracle True Cache</h3><p><a href="https://andersswanson.dev/2026/09/09/true-cache-in-memory-reads-for-oracle-ai-database/">Oracle True Cache</a> is a mostly-diskless, in-memory, read-only cache integrated into Oracle AI Database. True Cache is configured to replicate data from a primary database through <a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/sbydb/oracle-data-guard-redo-apply-services.html"><strong>redo apply</strong></a>.</p><p>Note that True Cache <em>is</em> Oracle AI Database, and is not a separate database/caching platform.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/726/0*NTkKPOHD_0OxdZKW" /></figure><h3>Start the Docker environment</h3><p>Optionally, set an admin password with the ORACLE_PWD environment variable (defaults to Welcome12345), and then run the compose script:</p><pre>export ORACLE_PWD=&#39;&lt;local-admin-password&gt;&#39;<br>docker compose up -d</pre><p>You should see the containers start up like so:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/583/0*Zwsi1JF96v0DMgnh" /></figure><p>Wait for oracle-free and true-cache containers to become healthy, and for true-cache-configure to exit successfully. That one-shot service registers FREEPDB1_TC on the primary, creates the TRUE_CACHE_DEMO user and demo schema objects there, then configures a TCPS endpoint and Redis dispatcher on True Cache.</p><p>On startup, a demo user is created in the FREEPDB1 database with ORACLE_PWD as its password. The exposed database ports are:</p><ul><li>Primary: localhost:1522/FREEPDB1</li><li>True Cache: localhost:1523/FREEPDB1_TC</li><li>True Cache Redis API: 127.0.0.1:6379 (TLS, local certificate)</li></ul><h3>Verify True Cache replication</h3><p>How True Cache stays currentWrites land on the primary. The cache serves reads.WRITEredo applyREADOracle AI DatabaseTrue CacheIn-memory replica · read-only</p><p>Once true-cache is healthy and true-cache-configure has completed, run the <a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/true-cache-docker-compose/test-true-cache.sh">test-true-cache.sh script</a>:</p><pre>./test-true-cache.sh</pre><p>The script adds two records in the primary database, and waits until True Cache replicates the data.</p><p>The data is then updated in the primary database, and True Cache is read again to verify the update.</p><h3>Verify the Redis API</h3><p>On container startup, the True Cache Redis API is enabled over port 6379, served over TLS with a self-signed certificate. This is a read-only, wire-compatible API for Redis clients to read data from True Cache <em>as if it were a Redis server</em>.</p><p>Read from True Cache with redis-cliredo applyread requestresultOracle AIDatabase&gt; redis-cliread clientTrue CacheRedis protocol over TCPS · localhost:6379</p><p>The <a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/true-cache-docker-compose/test-redis-cli.sh">test-redis-cli.sh</a> script verifies the Redis API against the container setup:</p><pre>./test-redis-cli.sh</pre><p>It checks for redis-cli before doing any database work, runs the SQL insert, read, update, read test with two new inventory lots, then authenticates over TLS and uses HGET to check both final stock counts through True Cache. It prints the Redis results as a table and exits with an error if the counts do not match.</p><h3>Run redis-cli directly against the database schema</h3><p>A simple coffee inventory is created on startup, and populated with data by the test scripts. We can directly connect redis-cli to the True Cache endpoint and query this data.</p><p>ColumnTypePurposelot_idNUMBERAutomatically generated primary key for a coffee lotcoffee_nameVARCHAR2(100)Coffee varietybags_in_stockNUMBER(6)Available bags, never negativeroasted_onDATERoast date, defaulting to today</p><p>Read lot ID 1 with HGET:</p><pre>REDISCLI_AUTH=&quot;$ORACLE_PWD&quot; redis-cli --tls --insecure \<br>  -h 127.0.0.1 -p 6379 --user TRUE_CACHE_DEMO \<br>  HGET &quot;true_cache_demo.coffee_inventory:lot_id=1&quot; bags_in_stock</pre><p>Note that the Redis API is read-only. Writes must still be made to the primary database; writes made with a Redis client will fail.</p><h3>Stop the environment</h3><p>When you’re done, tear down the docker compose environment:</p><pre>docker compose down</pre><p>The databases use container-local storage and are removed with their containers.</p><h3>Optional: Detailed Redis API Configuration</h3><p>The true-cache-configure service creates a local self-signed TCPS certificate and adds a PRE=REDIS dispatcher for the FREEPDB1 PDB service on port 6379. The helper also sets SHARED_SERVERS=1, which is required for the dispatcher to accept Redis connections.</p><p>The SQL client still uses the FREEPDB1_TC True Cache service. Oracle True Cache authenticates Redis connections as a database user and looks up rows with SQL keys. It does not provide a general Redis key-value store. Commands such as HGET and GET read database rows, while Redis write commands do not update the database. See Oracle’s <a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/odbtc/using-oracle-true-cache-redis-server.html">True Cache Redis Server guide</a> for supported commands and key syntax.</p><p>The helper runs read-only bind-mounted SQL*Plus scripts after True Cache starts: <a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/true-cache-docker-compose/oracle/redis/configure-true-cache.sql">primary demo schema setup</a> and <a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/true-cache-docker-compose/oracle/redis/configure-redis-dispatcher.sql">cache Redis dispatcher setup</a>.</p><p>Oracle Redis Server also works on an Oracle Active Data Guard standby, where it serves read-only standby data while redo apply runs. This is supported starting with Oracle AI Database 26ai Release Update 23.26.3; see Oracle’s <a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/sbydb/managing-oracle-data-guard-physical-standby-databases.html">Active Data Guard Redis Server documentation</a>.</p><p>The Redis test script uses redis-cli --tls --insecure because the local endpoint uses the self-signed certificate. Do not use --insecure for a remotely exposed endpoint.</p><h4>References</h4><p><strong>More about True Cache</strong></p><ul><li>For more information on how True Cache works for database read paths, see <a href="https://andersswanson.dev/2026/09/09/true-cache-in-memory-reads-for-oracle-ai-database/">True Cache: in-memory reads for Oracle AI Database</a>.</li><li>For Java integration tests, Spring Cloud Oracle provides<a href="https://oracle.github.io/spring-cloud-oracle/site/docs/database/testcontainers#testing-oracle-true-cache"> TrueCacheContainer Testcontainers support</a> for starting a primary and True Cache on a shared Testcontainers network.</li></ul><p><strong>Files</strong></p><ul><li><a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/true-cache-docker-compose/docker-compose.yml">Compose configuration</a></li><li><a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/true-cache-docker-compose/oracle/startup/00-export-primary-password-file.sh">Primary password-file startup script</a></li><li><a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/true-cache-docker-compose/oracle/redis/configure-true-cache.sql">Primary demo schema setup script</a></li><li><a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/true-cache-docker-compose/oracle/redis/configure-redis-dispatcher.sql">True Cache Redis dispatcher script</a></li><li><a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/true-cache-docker-compose/test-true-cache.sh">Primary-to-cache test script</a></li><li><a href="https://github.com/anders-swanson/oracle-database-code-samples/blob/main/true-cache-docker-compose/test-redis-cli.sh">Redis API test script</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=35de047f4a4c" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[UCP Metrics for Oracle AI Database]]></title>
            <link>https://medium.com/@anders.swanson.93/ucp-metrics-for-oracle-ai-database-7a9c79b72a45?source=rss-58a83f4309d6------2</link>
            <guid isPermaLink="false">https://medium.com/p/7a9c79b72a45</guid>
            <category><![CDATA[spring-boot]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[observability]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[Anders Swanson]]></dc:creator>
            <pubDate>Mon, 21 Sep 2026 21:19:01 GMT</pubDate>
            <atom:updated>2026-09-21T21:19:01.343Z</atom:updated>
            <content:encoded><![CDATA[<h3>Monitor connection pools for your database</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*YQ_MXGIQaP55HaHd.png" /></figure><p>UCP metrics are database-client metrics from <a href="https://andersswanson.dev/2026/07/13/connection-pooling-how-to-tune-oracle-ucp/">Oracle UCP (Universal Connection Pool)</a>, Oracle’s JDBC connection pool. Like HikariCP, UCP exposes pool-specific metrics: current and maximum connections, callers waiting for a connection, acquisition time, and pool lifecycle activity.</p><p>These metrics help separate a slow query from a slow path to a query. When an application exhausts its pool, waits to acquire connections, or accumulates latency before calls reach Oracle AI Database, UCP metrics show where to investigate.</p><h3>Configure and use UCP metrics</h3><p>To start collecting metrics, add the <a href="https://github.com/oracle/spring-cloud-oracle/tree/main/database/starters/oracle-spring-boot-ucp-micrometer">oracle-spring-boot-ucp-micrometer</a> dependency to your project. spring-boot-starter-actuator should be included to manage and scrape metrics:</p><pre>&lt;dependency&gt;<br>  &lt;groupId&gt;cm.oracle.database.spring&lt;/groupId&gt;<br>  &lt;artifactId&gt;oracle-spring-boot-ucp-micrometer&lt;/artifactId&gt;<br>&lt;/dependency&gt;<br>&lt;dependency&gt;<br>  &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;<br>  &lt;artifactId&gt;spring-boot-starter-actuator&lt;/artifactId&gt;<br>&lt;/dependency&gt;</pre><p>Then, configure your project to export metrics via application properties. For Spring Actuator expose the /actuator/metrics endpoint:</p><pre>management:<br>  endpoints:<br>    web:<br>      exposure:<br>        include: metrics</pre><p>For push-based collection, configure Spring Boot’s OTLP metrics exporter:</p><pre>management:<br>  otlp:<br>    metrics:<br>      export:<br>        enabled: true<br>        url: http://your-otel-endpoint:4318/v1/metrics<br>        step: 5s</pre><h3>Try it out with our UCP Spring Boot sample</h3><p>If you’d like to experiment with metrics using <a href="https://andersswanson.dev/2025/05/22/oracle-database-for-free/">Oracle AI Database Free</a>, I built an all-in-one Docker Compose sample that spins up Oracle AI Database, a Spring Boot app, and Grafana LGTM for testing.</p><p>To test the sample, <a href="https://github.com/oracle/spring-cloud-oracle/tree/main/database/starters/oracle-spring-boot-starter-samples/oracle-spring-boot-sample-ucp-jpa">c<strong>heck out the code here</strong></a> and run:</p><pre>export ORACLE_PWD=&quot;${ORACLE_PWD:-Welcome12345}&quot;<br>docker compose up --build -d</pre><p>Then, check out the Grafana dashboard at <a href="http://localhost:3000/">http://localhost:3000</a>.</p><p>A sample <a href="https://github.com/oracle/spring-cloud-oracle/blob/main/database/starters/oracle-spring-boot-starter-samples/oracle-spring-boot-sample-ucp-jpa/students-simulation.sh">load-generator script</a> is provided to utilize the connection pool.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/559/0*AuAc-DS7wbVzYkeP" /></figure><h3>Sample Grafana Dashboard</h3><p>To help you visualize statistics from UCP, I built a sample <a href="https://github.com/oracle/spring-cloud-oracle/tree/main/database/starters/oracle-spring-boot-starter-samples/oracle-spring-boot-sample-ucp-jpa/dashboards">Grafana dashboard</a> that uses these metrics. You can adopt this dashboard out of the box, or use it as a starting point for customization.</p><p>The dashboard includes information about the selected connection pool, like active connections, utilization rate, latency:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*9bIMhpZrEQbr4_Lo" /></figure><h3>What kind of UCP metrics are collected?</h3><p>OpenTelemetry <a href="https://opentelemetry.io/docs/specs/semconv/db/database-metrics/">database client metrics</a> are exposed under the db.client namespace with the <strong>db.client.connection.pool.name</strong> attribute set to the configured <strong>spring.datasource.oracleucp.connection-pool-name</strong>. Otherwise, he Spring DataSource bean name is used as the pool name.</p><p>The module also includes UCP-specific metrics under the ucp.connections namespace, which describe the connection pool in detail.</p><h3>References</h3><ul><li>Check out the <a href="https://oracle.github.io/spring-cloud-oracle/site/docs/database/ucp/#micrometer-metrics">UCP metrics documentation</a> for metric reference and setup details.</li><li>See <a href="https://andersswanson.dev/2026/07/13/connection-pooling-how-to-tune-oracle-ucp/">How to tune Oracle UCP</a> for details on tuning UCP in Spring Boot applications.</li><li>For more practical work on Oracle AI Database telemetry, browse <a href="https://andersswanson.dev/tag/observability/">my other observability posts</a>.</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7a9c79b72a45" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[With AI, it’s Cheap to Reinvent. But, Maintenance doesn’t go away.]]></title>
            <link>https://medium.com/@anders.swanson.93/with-ai-its-cheap-to-reinvent-but-maintenance-doesn-t-go-away-2e725d289860?source=rss-58a83f4309d6------2</link>
            <guid isPermaLink="false">https://medium.com/p/2e725d289860</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[coding]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[software-architecture]]></category>
            <dc:creator><![CDATA[Anders Swanson]]></dc:creator>
            <pubDate>Wed, 16 Sep 2026 19:03:16 GMT</pubDate>
            <atom:updated>2026-09-16T19:03:16.550Z</atom:updated>
            <content:encoded><![CDATA[<h3>To adopt, reuse, or invent.</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*rxu5zt3Da4SnZoBm.png" /></figure><p>Before you can finish evaluating a library or framework, an AI coding assistant can reinvent a custom implementation for you.</p><p>In the moment, <strong>that can be astonishingly productive</strong>.</p><p>LLMs are exceptional at reproducing prior art, which creates a tendency to favor bespoke software. Generating a new tool can <em>feel</em> faster than understanding, adopting, and configuring an existing one.</p><p>This is a version of the <strong>greenfield trap</strong>: Starting with a clean slate is psychologically and structurally satisfying. Reinventing feels clean because constraints are hidden.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*25OvXtnZRayOOUfF" /></figure><h3>The economics of creating software changed</h3><p>Software used to be incredibly expensive to create.</p><p>Even small software components required engineering time for design, programming, catching edge cases, testing, and documentation. The high cost of software drove developers to use existing libraries or frameworks whenever possible.</p><p>When you use an agent harness for coding, really anyone, including non-developers, can create a custom cache, scheduler, auth wrapper, or deployment script, in minutes. It may even work as intended in the first draft.</p><p>So Generative AI has reduced the cost of <strong>generating code</strong>.</p><p>But what about the cost of <a href="https://andersswanson.dev/2026/08/14/why-your-cloud-architecture-can-scale-users-but-not-costs/"><strong>owning software</strong></a><strong>?</strong></p><p>Ownership means comprehension: is the implementation understood? Are real failure paths tested? Is production behavior adequately monitored? Are vulnerabilities patched, users supported, upgrades implemented, and future migrations considered?</p><p>From experience, ownership isn’t as exciting as creating. Ownership introduces a sort of drudgery: fickle users, tricky bugs, and a <a href="https://www.first.org/newsroom/releases/20260615">never-ending parade of CVEs</a> to patch, test, and validate.</p><h3>Fast output vs. slow work</h3><p>With a decent agent harness and model, you can crank out code at a volume that wouldn’t otherwise be possible.</p><p>So Generative AI makes coding progress <strong>visible</strong> very quickly. The crux is that <strong>perceived speed isn’t sufficient evidence of delivered value</strong>. <a href="https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/">METR’s randomized study</a> found that experienced developers took longer on real tasks, despite believing they were faster.</p><p>It’s true that most established software components are reproducible by coding tools, as we’ve seen with the “rewrote project X in Y days” flavor of AI coding announcements.</p><p>But, the value of established software often comes from the <strong>years of testing, battle scars, and accumulated community trust</strong>.</p><p>Thus, the hidden work is<strong>:</strong></p><ul><li>Which assumptions did the generated implementation make?</li><li>What happens under concurrency or partial failure?</li><li>Were architecture decisions copied from outdated examples?</li><li>Who will understand the code six months from now? Will anyone need to?</li><li>Who owns the next version, patching, and software lifecycle?</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*KKOaSF-usMNKkpxj" /></figure><h3>Technical debt and reinvention</h3><p>Custom implementations will have fewer users, fewer deployments, and less accumulated knowledge. This doesn’t mean custom implementations aren’t useful; they definitely are. It means custom implementations require their own validation, which must be owned by creators. <a href="https://www.sciencedirect.com/science/article/abs/pii/S0164121216300176?via%3Dihub">Reuse is associated with less maintenance effort</a>.</p><p><strong>ownership debt</strong></p><p>As with any project, someone must maintain it: someone who can explain the behavior, patch vulnerabilities, and respond to change requests. This includes practices like tracking security requirements, design decisions, supply chain/BOM verification, and so forth. If you don’t accept this debt, your project is likely to face severe security challenges and much more painful debugging.</p><p>organizational fragmentation</p><p>If you give five teams a similar problem, you’ll likely get five generated wrappers, clients, validation frameworks, and deployment tools. This is inexpensive at the beginning, but complex in the long tail: You get duplicated documentation, inconsistent behavior, and repeated security vulnerabilities.</p><h3>Adopt, adapt, invent</h3><p>Before jumping to AI generation, consider your options.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*3lIlcfQFha7Mu5DC" /></figure><h4>Adopt</h4><p>Existing tools/components are great. Use them when you can. However, remember that <a href="https://andersswanson.dev/2026/04/29/the-exploit-window-is-shrinking-converged-databases-shrink-the-attack-surface/">dependencies bring their own risks</a>:</p><ul><li>abandoned maintainers;</li><li>vulnerable transitive dependencies;</li><li>incompatible licenses;</li><li>oversized APIs;</li><li>poor operational behavior;</li><li>upgrade pressure;</li></ul><h4>Adapt or Contribute</h4><p>Small adapters can extend existing components: something like a custom plugin or integration. If you have a need that isn’t represented in an open-source project, consider contributing back (but only after testing, vetting, understanding your change).</p><h4>Invent</h4><p>If you have domain-specific needs, build something new. Existing tools may either not meet your constraints, or the need is important enough to justify long-term ownership.</p><p>Notice that “AI can generate it” is not one of those reasons. AI tools are excellent at helping you discover, evaluate, and use existing tools. However, sometimes they introduce their own biases: For example, I wouldn’t ask Claude for opinions on competing products.</p><h3>Final Thoughts</h3><p>Implementation is both easy and abundant, which changes the calculus of assessing engineering effort.</p><p>Judgement becomes more valuable, changing the question from “Can we build this?” to “Should we build this?”</p><p>Strong teams will use generation selectively, and retain judgement on reuse, standards, and ownership. Knowing both what not to build and what efficient design looks like is still valuable.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2e725d289860" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[True Cache: in-memory reads for Oracle AI Database]]></title>
            <link>https://medium.com/@anders.swanson.93/true-cache-in-memory-reads-for-oracle-ai-database-c3757c98d3ce?source=rss-58a83f4309d6------2</link>
            <guid isPermaLink="false">https://medium.com/p/c3757c98d3ce</guid>
            <category><![CDATA[testing]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[Anders Swanson]]></dc:creator>
            <pubDate>Wed, 09 Sep 2026 20:54:30 GMT</pubDate>
            <atom:updated>2026-09-09T20:54:30.949Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*l_b09M7jls1IqCzd.png" /></figure><p><a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/odbtc/overview-oracle-true-cache.html">Oracle True Cache</a> is a mostly diskless, read-only cache and replica that stays synchronized through <a href="https://docs.oracle.com/en/database/oracle/oracle-database/19/sbydb/oracle-data-guard-redo-apply-services.html">redo apply</a>: Only frequently accessed blocks reside in memory. With True Cache, read-heavy queries can be offloaded to the cache replica, <strong>improving performance on the primary</strong>.</p><p>The cache keeps the same client, database model, and SQL interface as your primary database, allowing you to serve cache-friendly reads without another system or translating cache-specific objects.</p><p>In this article, we’ll explore how True Cache works, then describe how to test it with <a href="https://andersswanson.dev/2025/05/22/oracle-database-for-free/">Oracle AI Database Free</a> and <a href="https://andersswanson.dev/2025/05/29/easily-test-oracle-database-applications-with-testcontainers/">Testcontainers</a>.</p><h3>True Cache is Oracle AI Database</h3><p>External caches generally developers to serialize or map database data into a separate representation..True Cache isn’t limited by external semantics, instead acting as a read-only replica of the primary database. It caches Oracle AI Database objects and data types, and applies the same database security policies.</p><p>If your application already has a relational model, SQL queries, constraints, joins, JSON, or other Oracle data types — you can add an in-memory read path without changing the way data is accessed. There’s no cache-specific object shape or serialization layer. the same SQL can run unchanged on in-memory replica.</p><p>I’ve written a <a href="https://github.com/oracle/spring-cloud-oracle/blob/main/database/starters/oracle-spring-boot-testcontainers/src/test/java/com/oracle/database/spring/testcontainers/TrueCacheContainerIntegrationTest.java#L100-L131">True Cache integration test</a> that demonstrates this: It creates a table with a NUMBER key and a VARCHAR2 column on the primary, inserts a row, and later runs a normal SQL SELECT against the cache to retrieve the data.</p><p>Both the primary database and replica database clients use the Oracle JDBC driver (<a href="https://github.com/oracle/spring-cloud-oracle/blob/main/database/starters/oracle-spring-boot-testcontainers/pom.xml#L34-L38">ojdbc17)</a>. The application still decides primary or cache endpoint, but doesn’t need a secondary database client or platform to read cached data.</p><h3>Mostly diskless, in-memory caching</h3><p>True Cache satisfies queries from its buffer cache — a small amount of storage is required for standby redo log files, config files, and temp files. It’s a smaller persistent storage footprint than a full database replica.</p><p>The cache starts empty. On a miss, it fetches data from the primary, and it can read larger chunks while warming up. Once a block is cached, changes are applied through redo from the primary database. The frequently read part of the database ends up in memory without the application copying rows into a second schema or cache format.</p><p>The primary must run in ARCHIVELOG mode so redo can be shipped to the True Cache node. That prerequisite shows up directly in the test setup:</p><pre>private static final OracleContainer PRIMARY = new OracleContainer(OracleContainer.IMAGE_NAME + &quot;:latest&quot;)<br>        .withNetwork(NETWORK)<br>        .withNetworkAliases(PRIMARY_ALIAS)<br>        .withOracleSecrets(SECRETS)<br>        .withArchiveLog(true)<br>        .withForceLogging(true);</pre><h3>Cache freshness</h3><p>With True Cache, applications must still write to the primary database.</p><p>True Cache uses “redo apply” from the primary database, and then serves that data from memory. This is very high throughput, but If <strong>read-after-write</strong> consistency is required, I recommend reading from the primary.</p><p>I would use True Cache when:</p><ul><li>the workload has many more reads than writes;</li><li>the read path can tolerate a very small amount of redo-apply lag</li><li>the application benefits from keeping its Oracle SQL and data types instead of maintaining a second cache representation.</li></ul><p>Semantically, this is really no different than another read-cache. I just want to spell this out here to be clear about redo apply.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*UWgzMXapdChzNPRy" /></figure><h3>One client doesn’t mean one endpoint</h3><p>The True Cache application model includes two physical connections: a connection to the primary and a connection to True Cache. The application must choose its connection based on whether the operation needs to write new data, requires the latest data, or if it can use a cached read. This model is compatible with existing client drivers and programming languages.</p><p>For something like a Spring application, that could mean two DataSource instances, two JdbcClient instances, or a routing layer that selects the right one for each operation.</p><p>Oracle 26ai also supports one logical JDBC Thin connection that routes based on read-only state, plus OCI session-pool integration (<a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/odbtc/methods-connecting-true-cache.html?utm_source=chatgpt.com">Oracle connection methods</a>). With JDBC, you can configure this with oracle.jdbc.useTrueCacheDriverConnection=true and Connection.setReadOnly(boolean).</p><h3>Try it out with Testcontainers</h3><p><a href="https://github.com/oracle/spring-cloud-oracle/blob/main/database/starters/oracle-spring-boot-testcontainers/src/main/java/com/oracle/database/spring/testcontainers/TrueCacheContainer.java#L64-L164">TrueCacheContainer.java</a> joins primary and cache database containers with a shared network in <a href="https://github.com/oracle/spring-cloud-oracle/blob/main/database/starters/oracle-spring-boot-testcontainers/src/test/java/com/oracle/database/spring/testcontainers/TrueCacheContainerIntegrationTest.java"><strong>TrueCacheContainerIntegrationTest.java</strong></a>:</p><ol><li>The primary and cache are started on the same Testcontainers Network.</li><li>The cache uses the primary’s connection string</li><li>Tell the cache which Pluggable Database (PDB) service maps to which True Cache service.</li><li>Give the cache the primary password file it needs during startup.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*dZX0a2JYmWCFFZTp" /></figure><p>The integration test wires those pieces together like this:</p><pre>private<br> static final Network NETWORK = Network.newNetwork();<br>private static final OracleContainerSecrets SECRETS = <br>OracleContainerSecrets.withOraclePassword(OracleContainer.DEFAULT_PASSWORD);<br><br>private static final OracleContainer PRIMARY = new <br>OracleContainer(OracleContainer.IMAGE_NAME + &quot;:latest&quot;)<br>        .withStartupTimeout(Duration.ofMinutes(5))<br>        .withNetwork(NETWORK)<br>        .withNetworkAliases(PRIMARY_ALIAS)<br>        .withOracleSecrets(SECRETS)<br>        .withArchiveLog(true)<br>        .withForceLogging(true);<br>private static final TrueCacheContainer CACHE = new TrueCacheContainer()<br>        .withNetwork(NETWORK)<br>        .withNetworkAliases(CACHE_ALIAS)<br>        .withOracleSecrets(SECRETS)<br>        .withPrimaryDatabase(PRIMARY_ALIAS, OracleContainer.ORACLE_PORT,<br> &quot;FREE&quot;)<br>        .withPdbService(&quot;FREEPDB1&quot;, &quot;FREEPDB1&quot;, &quot;FREEPDB1_TC&quot;);</pre><p>The cache endpoint has to exist before the primary can advertise the corresponding service, and service registration changes the primary’s state, so the wrapper leaves it to the test or application owner.</p><h4>The test waits for a row to arrive</h4><p>Once both containers are running, the test confirms if a row committed to the primary can be read through the cache:</p><pre>try (Connection primaryConnection = PRIMARY.createConnection(&quot;&quot;);<br>     Statement statement = primaryConnection.createStatement()) {<br>    primaryConnection.setAutoCommit(false);<br>    statement.execute(&quot;CREATE TABLE true_cache_test (id NUMBER PRIMARY KEY, name VARCHAR2(30))&quot;);<br>    statement.execute(&quot;INSERT INTO true_cache_test VALUES (1, &#39;Ada&#39;)&quot;);<br>    primaryConnection.commit();<br>}<br><br>assertTrue(awaitCachedRow(), &quot;Expected True Cache to apply the committed primary row&quot;);</pre><p>awaitCachedRow() opens a connection to the cache and retries the query for up to two minutes. It catches SQL exceptions while the service is being created and redo is being applied, then waits one second before trying again.</p><p>The test passes only after the committed row became visible through the True Cache service.</p><h3>References</h3><ul><li>configuration details: <a href="https://github.com/oracle/spring-cloud-oracle/blob/main/site/docs/database/testcontainers.md#testing-oracle-true-cache">True Cache Testcontainers documentation</a></li><li><a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/odbtc/overview-oracle-true-cache.html">True Cache overview</a></li><li><a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/odbtc/methods-connecting-true-cache.html">Methods for connecting to True Cache</a></li><li><a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/odbtc/prerequisites1.html?utm_source=chatgpt.com">True Cache prerequisites</a></li><li><a href="https://www.oracle.com/database/truecache/faq/">Oracle True Cache FAQ</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c3757c98d3ce" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Why Your Cloud Architecture Can Scale Users But Not Costs]]></title>
            <link>https://medium.com/@anders.swanson.93/why-your-cloud-architecture-can-scale-users-but-not-costs-c55b59c59ce9?source=rss-58a83f4309d6------2</link>
            <guid isPermaLink="false">https://medium.com/p/c55b59c59ce9</guid>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[software-design]]></category>
            <category><![CDATA[database]]></category>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[Anders Swanson]]></dc:creator>
            <pubDate>Fri, 14 Aug 2026 18:57:27 GMT</pubDate>
            <atom:updated>2026-08-14T18:57:27.415Z</atom:updated>
            <content:encoded><![CDATA[<h3>Build simple, boring systems</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*LSSrTKHwK0YcHz9s.png" /></figure><p>If you’ve built software for a while, you’ve probably seen a similar architecture diagram: database for transactions/relational data, Redis for caching, Kafka for events, OpenSearch for full-text and keyword search, object storage as a data lake, and maybe a vector database for AI integration.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*WCPMQ8vV0lkk33oK" /></figure><p>There’s nothing wrong with these choices: each technology is good at its job. For example, you’ll find countless articles, papers, and engineering blogs extolling the virtues of Apache Kafka for asynchronous, service-to-service messaging.</p><p><strong>But how much is it going to cost to run and maintain all of this?</strong> Before on-boarding new component, ask yourself if the problem can be solved using existing platform capabilities. Simple is good. Boring is good.</p><h4>We optimize whiteboard architecture instead of the business system</h4><p>Imagine building an activity feed for a SaaS app: on the feed, users generate events, we store activities somewhere, and retrieves the right ones for each user. It also must scale as users and events grow.</p><p>During design discussions, the activity feed system might begin with a few core components:</p><ul><li>A RDBMS for relational records like users, preferences, activities</li><li>A message broker like Kafka to decouple application generated events from the service that builds activity feed</li><li>A cache like Redis to store frequently accessed feed items</li><li>A search engine like OpenSearch to index activity records for fast search and filtering</li></ul><h4>How much does the system cost to operate?</h4><p>Not just how much the infra/cloud licensing costs, but how much it costs the engineering organization to understand, test, maintain, monitor, secure, upgrade, and troubleshoot the system.</p><p><strong>Every component in a software system comes with this baggage</strong>. It creates operational work, consumes organization attention, and distracts engineers from building competitive, differentiating product features. Even for managed services, these costs don’t disappear (though they may be somewhat reduced).</p><p>This is particularly relevant for software businesses: architecture decisions eventually determine <strong>COGS</strong> (Cost of Goods Sold, essentially the metric of how much it costs to maintain and sell the software).</p><p>However, COGS is usually absent from architecture discussions. Whiteboard conversations focus on throughput, latency, scalability, and load projections. <strong>Costs and operational overhead become much clearer later.</strong></p><p><strong>At the extreme, I’ve seen systems where adding a new user actually costs more than the revenue that user generates.</strong></p><h4>Complexity compounds over time</h4><p>An initial architecture can be perfectly manageable. But successful products evolve, new features are brought in, and new components are added to the stack.</p><p>In isolation, each decision may be technically sound, but the system may still be expensive and optimize the wrong things. A small engineering team can effectively operate one database platform, but it’s significantly more difficult to build expertise in separate caching, event streaming, text search, and vector database systems.</p><p>At some point, infrastructure, operations, and maintenance can become the engineering group’s main source of work. A system that requires complex infrastructure, significantly more operational staff, and a wide collection of specialized technologies may not have scaled economically.</p><h4>The database can be more than a database</h4><p>To avoid the complexity of gluing together independent products, we can look toward modern, multi-model databases like Oracle AI Database or PostgreSQL.</p><p>Instead of stitching together independent products, the platform provides integrated capabilities:</p><ul><li>relational data</li><li>JSON/document storage</li><li>graph</li><li>spatial</li><li>vector search</li><li>analytics</li><li>AI capabilities</li><li>transaction processing</li></ul><p>Take our activity feed, for example: each separate component could be mapped to in-database features:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/921/0*5yIFg3pHZjzAdemN" /></figure><p>And so forth. Most, if not all capabilities can be operated on a single database these days. This doesn’t mean you have to build everything on the same database, but it can be a dramatic simplification.</p><h4>A note on testing</h4><p>As the number of moving parts grows, it’s more and more difficult to effectively test the system without a fully deployed stack. I’ve written a lot of tests for these kinds of systems, and full-blown integration tests are challenging to build.</p><p>Interestingly, an integrated data platform like a multi-model database is often easier to test.</p><p>When I write an integration test, I don’t need to spin up and glue together a complex stack. I can create one (1) database container, run my schema setup, and connect my applications using the provided connection string. <strong>This makes integration tests simple and reproducible, with startup times measured in seconds.</strong></p><p>This even allows complete local testing with frameworks like <a href="https://andersswanson.dev/2025/09/11/learn-testcontainers-java-with-oracle-database-free/">Testcontainers</a> and <a href="https://andersswanson.dev/2025/05/22/oracle-database-for-free/">Oracle AI Database Free containers</a>. I have dozens of examples doing exactly this with various (and combined) database features <a href="https://github.com/anders-swanson/oracle-database-code-samples"><strong>here</strong></a>.</p><h4>References</h4><ul><li><a href="https://github.com/anders-swanson/oracle-database-code-samples">Polyglot, multi-model database code samples</a></li><li><a href="https://andersswanson.dev/tag/testcontainers/">Integration testing multi-model database applications with Testcontainers</a></li><li><a href="https://andersswanson.dev/2026/08/03/no-separate-database-required-real-time-multi-model-data-processing/">Sample multi-model database app combining event streaming, relational database, vector database, and spatial data on a single engine.</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c55b59c59ce9" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>