Introduction In the previous post, you saw how you can use tools to add information to an LLM query. In this post, we’ll see another method of adding information to an LLM called RAG, or Retrieval-Augmented Generation. The idea of RAG is that you want the LLM to have access to information that wasn’t available to it when it was initially trained. You do it by storing documents in your own database along with their embedding. I won’t go into the technical details of embedding, but think of it as a way to convert a piece of text into a vector. The magic is that if two pieces of text have…
Introduction LLMs are great, but they are trained on public data sets. In some cases, you need the LLM to use data that’s not publicly available or that’s frequently changing. There are several ways to make such data available to LLMs: Tool/function calls Retrieval-augmented generation (aka RAG) MCP In coding agents, you can also add skills. In this post we’ll focus on function calling. How Does It Work? When interacting with an LLM, you can provide a description of available tools if the model supports tool calling. If the LLM reasons that the best answer is to use one of the tools, it will…
Iteration has long been one of the more fragmented areas of Go, with developers relying on ad hoc patterns to traverse custom data structures. This article explores the range-over-functions experiment, a proposed evolution of the language that introduces a standardized iterator model while preserving Go’s familiar for range syntax. Using the new iter package and sequence abstractions, it shows how iteration logic can be expressed more clearly, flexibly, and idiomatically. Originally published in April 2024, the concepts remain highly relevant as Go continues to evolve toward more expressive…
Introduction In this post you’ll see how you can create a system that allows users to query a relational database using plain English. This allows users not familiar with SQL or business intelligence systems to get insights from data. Setting Up If you want to follow along, you’ll need to clone the code from the GitHub repo. This will download the code, and the database file containing the data (bikes.ddb) Note: The data is from the Austin Bike Share dataset.
Kubernetes memory limits introduce a subtle but critical interaction with the Go runtime that can determine whether a service runs efficiently or fails under load. This article explores how Go manages memory under normal conditions and what changes when Kubernetes enforces hard memory constraints. Using controlled load testing, it shows why Go is generally excellent at self regulating memory, how OOM events emerge once limits are imposed, and how tools like GOMEMLIMIT can be used to align the runtime with container boundaries. Originally published in July 2024, the guidance remains highly…
In a recent livestream with JetBrains, Vitaly Bragilevsky sat down with Herbert Wolverson, our Lead Rust Consultant and Instructor here at Ardan Labs, to talk about everything Rust developers – beginners and pros alike – are curious about. Watch the full livestream replay on the Ardan Labs Channel and check out the GitHub repo with Herbert’s prepared answers and code samples. Below is a short summary of the 23 questions that were covered during the livestream.
Understanding how your data structures interact with hardware is one of the most powerful ways to improve application performance. This blogpost explores how CPU caches influence speed and how thoughtful struct design in Go can yield massive gains. Through a real-world case study, it shows how replacing a large embedded array with a slice improved performance by more than 40 times by reducing cache misses and improving data locality. Originally published in July 2023, its lessons remain highly relevant today for developers optimizing for memory efficiency and cache-aware programming.
Kubernetes CPU limits can look straightforward on the surface, but their impact on application performance is anything but simple. This article unpacks how Go services interact with Kubernetes CPU throttling and why a seemingly harmless configuration such as setting a limit of 250m can dramatically constrain performance in production. First published in February 2024, its insights remain just as relevant today for anyone running Go applications in containerized environments and for developers who want to avoid costly slowdowns.
Go’s garbage collector is designed not only to manage memory safely but also to pace itself intelligently, striking a balance between low latency and high throughput. This blogpost explores how the GC adapts its pace to workload demands, demonstrated through both sequential and concurrent program examples, and why reducing allocations per unit of work is the most effective way to lighten its load. Originally published in 2019, its core principles remain just as relevant today, offering Go developers a deeper understanding of the runtime’s adaptive behavior and confidence that the GC can find…
Originally published in 2019, this article is part two of a three-part series exploring Go’s garbage collector. Though the Go runtime has continued to evolve, the performance principles covered here remain highly relevant today. This installment focuses on practical techniques for analyzing and reducing garbage collection (GC) overhead in real-world Go applications. It walks through how to interpret GC traces using GODEBUG=gctrace=1 and uncover allocation hotspots with pprof, with a clear message: reducing unnecessary allocations, especially in tight loops, has a direct and measurable impact…
This article was originally published in 2018, yet its core insights into Go’s garbage collection model remain highly relevant for developers today. While some implementation details of Go’s runtime have evolved, the foundational concepts explored here—such as the semantics of the tri-color mark and sweep algorithm, Stop The World (STW) events, and GC trace interpretation—are still essential to understanding how Go manages memory. Whether you’re optimizing performance or deepening your knowledge of Go internals, this post continues to offer a clear and practical guide to working with the…
Introduction In part 1 we took a higher level view on serialization in general and JSON in specific. In part 2 we looked at emitting JSON. In part 3, we’ll look at an issue you might encounter when consuming JSON, Zero vs NULL field values. To clarify the definition of NULL, this means the absence of value. So here is the question: Given a field in a Go struct set to its zero value, how do you know that zero value was set by the user or it’s zero because it was never provided?
Although originally written in 2018, the following concepts remain essential for developers working with concurrency. This blogpost focuses on concurrency, distinguishing it from parallelism by defining it as “out of order” execution. It emphasizes the importance of understanding workload types—CPU bound (e.g., summing, sorting) and IO bound (e.g., file reading)—to assess when concurrency is appropriate. Through practical examples and benchmarks, it shows that parallelism boosts performance for CPU bound tasks, while concurrency alone benefits IO bound workloads. The post underscores that…
This blogpost is the second installment in a three-part series exploring the mechanics and semantics of the Go scheduler. Despite being published in 2018, the content remains relevant today, as the Go scheduler’s design continues to influence the development of efficient and scalable concurrent systems. In this post, we will go into the inner workings of the Go scheduler, discussing its components, such as the Global Run Queue (GRQ) and Local Run Queue (LRQ), and its behavior, including context switching, work stealing, and the handling of synchronous and asynchronous system calls. By…
Although this blogpost was originally published in 2018, the concepts and principles discussed remain crucial for building efficient and performant multithreaded applications in Go now in 2025 (Go 1.24.0). As the Go ecosystem continues to evolve, understanding how the Go scheduler interacts with the operating system scheduler is more important than ever. This three-part series provides a comprehensive overview of the mechanics and semantics behind Go’s scheduling, starting with the fundamentals of the operating system scheduler.
Although first introduced in 2014, the Context package remains a crucial component of Go programming, enabling efficient management of request-scoped data, deadlines, and cancellation signals. As the Go ecosystem continues to evolve, understanding the Context package’s semantics is vital for developing reliable and maintainable software. This blogpost provides an in-depth exploration of the Context package’s semantics, highlighting best practices and common pitfalls to help developers effectively leverage this powerful tool.
Introduction In part 1 we took a high-level view on serialization and JSON. In this part, we’ll roll our sleeves and start working with JSON, focused on emitting JSON. You might think this is a basic topic, but there is much more to it than just calling json.Marshal. json.Marshal vs json.Encoder The encoding/json package has two main APIs: Marshal and NewEncoder. The Marshal function returns a []byte while the NewEncoder function will write to an io.Writer. The question is: When should you use one API over the other?
Introduction: In the final episode of the Optimizing Databases on Kubernetes series, Jérôme Petazzoni dives into advanced backup and recovery techniques for PostgreSQL, showcasing how CNPG (Cloud Native PostgreSQL) and ZFS snapshots ensure durability and fast recovery in production environments. This episode focuses on safeguarding data with comprehensive backup strategies, point-in-time recovery, and rapid database cloning, while leveraging ZFS’s efficiency for optimized storage performance. Comprehensive Backup Strategies: Exploring backups with CNPG, including write-ahead logs (WAL) and…
Introduction: In Episode 4 of the Optimizing Databases on Kubernetes series, Jérôme Petazzoni benchmarks the performance of various Kubernetes storage classes, including cloud block storage, ZFS, and Rancher’s Local Path provisioner. This episode dives into the practical aspects of measuring transactions per second, storage efficiency, and durability, offering insights into selecting the right storage solution for database workloads. Through detailed performance comparisons, Jérôme highlights how features like ZFS compression can optimize resource usage and boost database throughput.
Introduction: In Episode 3 of the Optimizing Databases on Kubernetes series, Jérôme Petazzoni introduces ZFS, a versatile file system renowned for its features like compression, deduplication, and snapshots. Leveraging ZFS with Kubernetes, Jérôme demonstrates how to create efficient and flexible storage solutions, complete with automated setup and configuration using tools like OpenEBS LocalPV. This episode showcases the potential of ZFS to optimize storage performance and reliability in containerized environments. Overview of ZFS: Exploring the features of this advanced file system, from…
Read at the source
Your visit, your choice.
Optional Google Analytics helps us understand visits. Microsoft Clarity records masked interactions to improve the site. Optional tools stay off unless you choose them. Privacy details.