Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
153141 stories
·
33 followers

C# Records

1 Share

Introduction

C# records and record structs are relatively new. Records offer some advantages over regular classes and structs, which I will cover here, but they are essentially another way to declare them. This is another back to basics article.

We essentially use records for:

  • Entities, such as the ones we would store in a database
  • Value objects/Data Transfer Objects
  • Immutable classes

Let's now see how to work with them.

Syntax

Records are declared like this:

public record Point(int X, int Y);

Or like this, with the exact same meaning:

public record Point
{
public int X { get; init; }
public int Y { get; init; }
}

And we instantiate a record just like any other class or struct:

Point p1 = new(1, 2);
//or
var p1 = new Point(1, 2);

We can, of course, use positional or named arguments:

var p1 = new Point(Y: 2, X: 1);

It is possible to add new properties (or methods) besides the ones declared on the primary constructor, we don't need to live with just those properties:

public record Point(int X, int Y)
{
public int? Z { get; init; } //init-only
public string? Name { get; set; } //get and set public double DistanceToOrigin => Math.Sqrt(X * X + Y * Y); //get only }

Immutable by Default

Properties declared on the primary constructor on a record are init-only, meaning, you cannot change their values:

p1.X = 10; //error: init-only property X

If we, however, declare properties in the old style, we can have getters, setters, or initialisers.

Reference and Value Type Records

record is actually a class, and a record struct is a struct, meaning, they are either reference or value types. We can, of course, have nullable records too.

Limitations

A record can only inherit from another record, or Object (the default). A record struct cannot inherit from anything. Both can implement any number of interfaces. Only records can inherit from another record.

For a list of common errors when using records, please see this.

Cloning

Cloning is implemented out of the box, and but we can change some properties on the clone, this is using a with expression:

Point p2 = p1 with { Y = 3 }; //all properties are the same except Y
Point p3 = p2 with { }; //shallow clone

Deconstruction to Tuples

It is also possible to deconstruct a record to a tuple too:

var (x, y) = p1;

Hash Code and Equality

Equality (Equals) and hash code (GetHashCode) are properly implemented for all members, and are stable:

var h1 = p1.GetHashCode();
var h2 = p2.GetHashCode(); // != h1
var h3 = p3.GetHashCode(); // == h1

Comparison is always by value, every property of the record is compared: Equals and the == operator look at what's inside the record.

String Representation

The string representation of a record (ToString) includes all properties of it:

var str = p1.ToString(); //"Point { X = 1, Y = 2 }"

This raises an interesting problem: if we have a property that points to the same object, we will get an exception while calling ToString:

record Data(string Name)
{
public Data? Parent { get; set; }
}

var data = new Data("Foo");
data.Parent = data;

data.ToString(); //exception because we enter an infinite cycle

Conclusion

I find records to be a great addition; they solve the problem of us having to define equality and hashing, and are particularly good for immutable types. I also like how we can easily get a string representation of all of its contents.

Read the whole story
alvinashcraft
just a second ago
reply
Pennsylvania, USA
Share this story
Delete

Learn T-SQL With Erik: Heap Table Problems with Updates and Deletes

1 Share

Learn T-SQL With Erik: Heap Table Problems with Updates and Deletes


Chapters

Full Transcript

Erik Darling here with Darling Data. Excited to finally get back to some more SQL Server specific related content, not about monitoring tools or vectors or any of the other stuff that we’ve been talking about here on the Chan recently. Today we’re going to get back to some of the Learn T-SQL with Erik content, recent updates, and we’ll see some more updates as soon as the material is fully tech-reviewed and revised. But today we’re going to talk about heaps. And I know what you’re going to say, Erik, heaps are not T-SQL. And I agree. But part of being an advanced T-SQL practitioner is understanding different storage formats that SQL Server uses and some of the upsides, downsides, and sidesides of them. So in today’s video, we’re going to talk about heaps generally, one condition that they avoid and two conditions that they are prone to. This is also, we’ll talk briefly about sort of the proper place in the world for heaps as well. Before we do that, if you look down in the video description, you will find all sorts of helpful links. You can hire me for consulting. You can even buy my training, including the full version of the small snippet of training that we’re going to be talking about in today’s video, which is the Learn T-SQL with Erik course. There is a discount code down there if you want to check that out. You can become a supporting member of the channel. You can ask me office hours questions. And one thing, I mean, it’s not just exactly a link that you can click, but there are buttons that allow you to like, subscribe, and share these videos with people who you feel they might demonstrate some value to. So, you’ve got all those things available for free and for pay. Another thing for free is my SQL Server performance monitoring tool. That’s at my GitHub repo. The link is there, but the link is also down in the video description as well. It’s totally free, totally open source, doesn’t require you to sign up, give me your email or anything like that. It’s just a bunch of T-SQL collectors that go and monitor all the stuff that I would monitor if I really wanted to monitor a SQL Server.
And so that’s what I do. It captures all the stuff that you would care about. It’s got a Nox-style dashboard if you want to just get a quick glance at server health. And if you are a fan of our new robot overlords, it has optional, built-in, you have to opt in to use them, MCP servers. So you can have your favorite robot talk to your performance data and just your performance data uses a bunch of read-only tools to do that. And it can give you all sorts of potentially helpful, dangerous advice that I am not liable for. Anyway, I’m going to be getting out and about in the world, all sorts of places that I am excited to be.
SQL day Poland will be May 11th and 13th, right after Pass on Tour Chicago, which is May 7th and 8th. So I have like a day to get there. I think I land in Poland on like the 10th in the morning. So I’m sure by the 11th, I’ll be in great shape, great spirits. Take some Benadryl with me on that one.
Then I will be at Data Saturday, Croatia, June 12th and 13th. And I will be at a Pass Summit in Seattle, Washington, November 9th through 11th. Excited to see everyone all over the place.
And of course, you know, Seattle and Chicago I’ve been to, but Poland and Croatia, it’s some first-timers for me. So the old man’s going to get some new stamps in the passport. Excited about that. So hopefully I meet nice people along the way.
But it is, well, actually, when does this get published? This, this, I might need to change this to the new image. Let’s see. Because this might be the April 1st video.
And this is not a, an April Fool’s joke video. So we are, we are firmly into baseball season and we are excited about that. And I am excited by this, this fellow right here with his floating, floating hot dog.
I don’t know. I don’t know how that’s staying weird. Maybe he’s got a little hot dog holster there. I don’t know. Everyone else holding their beer and hot dogs firmly.
I don’t know where this guy in red showed up from. All of everyone else is wearing blue, but I don’t know. Anyway, good job. Data baseball. We’re, we’re in there.
Anyway, let’s switch over to SQL Server Management Studio for the first time in a long time. And let’s talk a little bit about heaps. Now, heaps have two things about them that make them, sort of pigeonhole them into, like, like an append-only territory.
And that is that updates and deletes do not get along terribly well with heaps. Heaps are, of course, tables that do not have a clustered index on them. They may, they may very well have a primary key of the non-clustered variety.
They may have any variety of nonclustered indexes or other constraints on them. But without a clustered index, they are still a heap, meaning they are a collection of unordered pages. So you have the base heap table and then other stuff.
We’re going to talk about clustered indexes, which I prefer to call clustered tables these days. Next. Tomorrow’s video. Don’t get ahead of yourself.
Anyway. Whew. Feeling good. Feeling a little jazzy. So if we look at, let’s actually run this procedure called load elhepo. If you’re interested in getting the code or seeing what all this stuff does behind the scenes, you can buy the course and see all that stuff.
But I’m just giving you a small taste of what’s in there. So if we look at forwarded records for the heap at current, we will see that we do not have any. This is the column that we care about.
So we, of course, inserted a little over a million rows into the heap itself. And then we built a nonclustered index on the heap. So we’ve got that. That’s about all we care about here.
Okay. Because you’ve got nothing to show. So this query will help us find sort of the middle value in the table. And the reason I want to find the middle value is because I want to update about half of it. Because updating half of it in one go sort of simulates, you know, sort of data updates over time that you might see if you are misusing heaps and putting them in a position where they are constantly updated but never, like, truncated or cleared out and then reloaded with other data.
Or just the data is not just append only because that’s where things get into trouble. So if we look at these two queries as they are today, we will see that they have absolutely no forwarded records. Well, I mean, okay.
So if we run these two queries and we look at the before and after of the forwarded record counter, we will see that it is the same after both of them, right? 9582.
So we did not encounter any forwarded records. I just showed you the table with none in there. And as far as page splits a second go, we have this number. This number might change a little bit just because there might be other stuff going on in the background on my server. But this number won’t change because of the heaps.
But what I’m going to do is I’m going to update about half of the table and I’m going to inflate one of the columns a bit just to create some forwarded records. We’re going to fill up some data pages so that SQL Server is going to say, well, this row no longer fits there. We need to move you somewhere else.
I talk more about the mechanics behind that in the training course. But for now, just understand that when you update a heap and rows no longer fit on a page, SQL Server moves those rows silently. Well, sometimes not so silently to other pages where they fit.
So, again, this might change a little bit because of other stuff going on, but I don’t think that number changed at all. That looks the exact same to me. The number that will change is if we go back to that original query and we look at this, we will now see that we have a whole bunch of forwarded fetches against the heap.
The nonclustered index doesn’t have any forwarded fetches in it because nonclustered indexes do not behave the same way heaps do. But that heap now has 475,762 forwarded fetches, right? So, that is the number of rows that have moved about in the heap.
So, if we run these queries again and we just look at the forwarded fetches here, or in this case, they are called forwarded records a second, we’ll get these two queries will eventually run and finish, and this counter value will go from 485344 to 961106, right? So, that just about adds the number of forwarded records there are in the table to the counter there.
It’s pretty amusing, right? Now, of course, you can rebuild the table to get rid of the forwarded records, but that will also rebuild the nonclustered index. At the same time, if you have a heap with lots of nonclustered indexes on them, fixing the forwarded record problem might be a bit painful for you.
It’s pretty common if it’s a large heap to drop or disable the nonclustered indexes, rebuild the table, and then recreate the indexes sort of one at a time, so you don’t have to do that all in one big thing. So, if I turn on query plans, and I look at the alter table rebuild for this, the query plan, when this eventually finishes, shows two things, right?
We see the first section, if my little grabby friend will help here. So, we have the insert into the heap here, right? So, this is rebuilding the table, and then this is rebuilding the nonclustered index, right?
So, we do this work twice. Notice that we have to sort the data here before we do the index insert. That is a nonclustered index key order being sorted.
So, forwarded records, if you’re updating heaps a lot, they can really add up. They can really slow queries down eventually. It sort of introduces a strange sort of jagged I.O. pattern because you have to start scanning through the heap, and then if you hit a forwarded record, you have to jump to where that row lives, and then restart, then come back and do this, and jump to the next forwarded record, and you just kind of end up doing a lot of jumpy, jumping-around work that you don’t want to do when you’re trying to scan through something nice and easily.
The next thing that heaps can have trouble with is deletes. So, unless a delete acquires a table-level lock, either naturally through lock escalation or by specifying a tab lock, potentially x hint, then pages will not automatically be deallocated from a heap, which means that if a page completely empties out of rows, SQL Server is not in a rush to be like, hey, we should get rid of this page because it doesn’t have anything on it.
SQL Server uses that as sort of an optimization, kind of figuring, well, if we’re going to load more data in here, we might need these pages around, and we could just reuse these empty pages, and then, you know, we wouldn’t have to create a new page.
We could save ourselves some time, right? Optimize the inserts. Heaps are meant for, right? Fast insert. No sorting data. No indexes to keep track of. Just whoosh, data in.
All right. So, one thing that is interesting here is that if you have a row versioning isolation level, even a tab lock hint won’t help here.
It’s a whole thing. But you may see similar behavior if your heap contains lob columns, varchar, nvarchar, var binary max, XML, vector, JSON should probably be on that list, or essentially any column that could cause row overflow data, even if you have just very wide rows.
Page deallocation is completely disabled, even with a tab lock hint. The data does not actually have to use lob or overflow to separate pages. The mere possibility, that possibility existing, is sufficient for that to happen.
This is, of course, by design. This is not a bug. Don’t report it to Microsoft. Don’t say, Eric said this is bad and you should fix it. This is the way it works.
I’m just trying to teach you how it works. That’s my job here. So, if we look at indexes on the heap table, we will see that we have two of them still. We have our nonclustered index, which is about 17 megs, and we have our heap, which is about 260 megs.
We can see the in-row used page count over here and the row count that we have in the table of 104, 8576, which is exactly how many rows I loaded into it.
Now, we’re going to use everyone’s favorite. We’re going to use a cursor to delete through the table. And we are going to do that in such a way that we do not acquire a table-level lock, right?
So, we’re going to run all this stuff. Do-do-do-do-do. And I turned off query plan, so this shouldn’t take too long. It should be about 10 seconds or so.
But if it takes longer than that, well, I just might start drinking on camera. Cool. Nine seconds on the nose. I guess I’ve run this demo before, haven’t I?
So, now when we look at this, we’ll see a couple interesting things. The nonclustered index, that’s this one right here, is down to 0.16 pages, right? Or megs of pages assigned to it.
But the heat table still has all 260 megs assigned to it, right? And still has all of this, even though it has no rows in it, right? There is not a single row in there, but it is still the same size.
Every page remains after that delete, right? So, if I run this, if I checkpoint, right? And I run these two queries, let’s turn query plans on, right? Like, none of these, neither of these return any rows, right?
But notice that we still do this table scan here, right? And if we look at the statistics I.O. output for this, we still do a whole bunch of logical reads against the heap, right?
But we do almost none against the nonclustered index, right? Coming down here, that’s what was happening. This is our index scan, which was, you know, the index that now has no pages assigned to it.
But this thing up here still scans every single page, right? Fun times are afoot. So, if you’ve got heaps, if you’ve got tables that do not have clustered indexes on them, remember that they are flat structures.
They are not tree-shaped. They use something called a row identifier or a RID, which is the file, page, and slot number combination for the row to, instead of a clustering key, to identify rows.
They’re tracked by something called the index allocation map. That is the IAM. And it tracks about four gigs of heap data apiece, or they track about four gigs of heap data apiece.
The good stuff that you can get out of heaps are fully parallel data loads because you don’t have any indexes to maintain. You don’t have to deal with page splits on updates, just forwarded records. There’s less logging overhead for bulk operations.
And empty pages will stick around. They’re not deallocated in case you want to do some more inserts. The not-so-good stuff is forwarded records when rows grow and move pages. We saw that with the updates.
We saw that pages where all the rows got fully deleted off of them don’t get deallocated automatically, and the scans still read a lot of empty pages.
That can be really surprising and really impact performance if you’re not paying attention to it. You need table locks or lock escalation for page deallocation to occur. If you’re trying to get the fully parallel insert into the heap, and if you have any nonclustered index on there, it messes them up.
Role versioning isolation levels will prevent page deallocation, even with the tab lock hint. Rebuilding the heap also rebuilds all the nonclustered indexes at the same time. Forwarded records make nonclustered index lookups a little bit more expensive because you not only have the lookup, but now you also have the following page forwarded records around in there.
If you really just need to dump a bunch of data into a table quickly, heaps are probably the way to go. And simple recovery model will really help with minimal logging.
But, you know, a lot of people look upon heaps as if they are evil structures, but really, you know, they’re just things that are… They’re very specialized structures with very specific purposes that unfortunately have been sort of abused and misused by people over the years and have gotten a bad name.
Sort of like cursors. Ha! See how I did that. Anyway, thank you for watching. I hope you enjoyed yourselves. I hope you learned something. I hope you’ll check out the full training course. Again, that’s available.
There’s a link down below where you can get the Learn T-SQL with Erik course. And, yeah, that’s that. All right, cool. See you in tomorrow’s video where we will talk about clustered indexes. Won’t we then?
All right. See you then.

Going Further


If this is the kind of SQL Server stuff you love learning about, you’ll love my training. Blog readers get 25% off the Everything Bundle — over 100 hours of performance tuning content. Need hands-on help? I offer consulting engagements from targeted investigations to ongoing retainers. Want a quick sanity check before committing to a full engagement? Schedule a call — no commitment required.

The post Learn T-SQL With Erik: Heap Table Problems with Updates and Deletes appeared first on Darling Data.

Read the whole story
alvinashcraft
25 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

There is no Install – it’s ‘Stage’ and ‘Register’

1 Share

“Is ContosoParts.msix installed?” is a common – but misleading – question

The term install is not a formal concept in MSIX.

This may seem paradoxical for a deployment technology, but it makes perfect sense once you understand MSIX deployment’s core architecture.

Deployment Requests

A caller makes a deployment request to the deployment engine via the PackageManager API to perform some activity. This may involve adding a package to the system, removing one, or performing another management operation. Deployment requests include:

  • the requested action
  • the target package
  • API parameters supplied by the caller
  • contextual information about the requesting user (e.g., user ID and security context)

The system queues the request in the deployment engine’s memory for processing by the deployment pipeline.

The Deployment Pipeline

The deployment pipeline processes requests through an ordered set of steps. The pipeline may short-circuit or skip some steps, depending on the request and system state. At its core the processing consists of three primary stages:

  • Index
  • Stage
  • Register

Index

Parse the incoming package’s metadata (for example, AppxManifest.xml) and store that information along with system metadata such as the intended installation path.

Stage

Create the package’s target installation location (also known as the package directory, or pkgdir).

Acquire the package payload and extract it into the pkgdir, and apply appropriate ACLs to secure the content.

Register

Associate the staged package with a specific user profile.

This may involve, among other actions:

  • creating Start Menu entries
  • registering file type associations
  • configuring runtime data

Registering the package effectively makes it usable for that user.

Per-Machine vs Per-User

Indexing and Staging are per-machine activities. The system performs these once per machine.

Staged packages are stored and shared across all users on the system, with the pkgdir secured to allow Read and Execute access — but not Write access — to users.

Registering is a per-user activity.

This separation enables MSIX to alter the set of available packages for one user without affecting others. This isolation — and the controlled servicing it enables — is a cornerstone of MSIX’s design and is reflected in many platform behaviors and APIs.

So… Installing?

To make a package available for a user for the first time, a caller might invoke an Add API:

var packageUri = new Uri("C:\\Packages\\ContosoParts.msix");
var options = new AddPackageOptions();
var packageManager = new PackageManager();
var result = await packageManager.AddPackageByUriAsync(packageUri, options);
...

This sends an Add request to the deployment engine, which:

  • Indexes the package
  • Stages the package into a new pkgdir
  • Registers it for the user

At no point did we technically “install” anything.

Even though we just installed the package 🙂

The post There is no Install – it’s ‘Stage’ and ‘Register’ appeared first on Inside MSIX.

Read the whole story
alvinashcraft
53 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

OAuth 2.1 Made Simple: The Only Flows You Need

1 Share

Back in 2019, Dominick Baier, Duende Cofounder and Security subject-matter expert, wrote a prophetic post called "Two is the Magic Number", a riff on De La Soul's "Three is the Magic Number", arguing that you only needed two OAuth flows to cover every real-world scenario. At the time, it was a bold simplification. OAuth 2.0 had shipped with a sprawl of grant types: Implicit, Resource Owner Password Credentials, Authorization Code without PKCE, Client Credentials, and the ecosystem dutifully built tutorials for all of them. The result was confusion. Developers picked the wrong flow, shipped insecure apps, and blamed OAuth itself for being "too complicated."

Dominick was right, and the standards body agreed. OAuth 2.1 formally removed the footguns, mandated PKCE on every authorization code grant, and left us with a protocol that is dramatically simpler to learn and harder to misuse. If you're building on .NET 10 in 2026, this is the only article you need. Two flows cover almost everything. A third handles the edge case. Let's go.

Read the whole story
alvinashcraft
4 hours ago
reply
Pennsylvania, USA
Share this story
Delete

CO2 Levels In the Atmosphere Hit 'Depressing' New Record

1 Share
Atmospheric carbon dioxide hit a new record in April, averaging about 431 parts per million at NOAA's Mauna Loa Observatory. That's up from under 320 ppm when the site began measurements in 1958. Scientific American reports: Greenhouse gases, such as carbon dioxide, are measured as a proportion of the total atmosphere. The numbers are presented as the number of molecules of a particular gas out of a million total molecules, or ppm. Climate scientist Zachary Labe of Climate Central, a nonprofit that researches climate change, says the new record is "depressing" but not unexpected. "It's just another sign that carbon dioxide continues to increase in our atmosphere as our planet continues to warm," he says. "For many climate scientists, this is just 'here it is again, another record in the wrong direction.'" Labe explains that the amount of CO2 in the atmosphere tends to peak in April each year as decaying plants release greenhouse gases after winter. Some of that CO2 gets reabsorbed by plants as they grow during the warmer months. But NOAA's data show a worrying trend, with the average monthly amount of CO2 steadily increasing. [...] Although the amount of CO2 in the atmosphere has continued to rise, there was a reduction in U.S. emissions in 2023 and 2024. That trend, however, was reversed in 2025, at least partially because of the increased electricity demand from artificial intelligence data centers. Still, Labe says there are reasons for optimism as the use of renewable energy sources such as solar and wind expands.

Read more of this story at Slashdot.

Read the whole story
alvinashcraft
5 hours ago
reply
Pennsylvania, USA
Share this story
Delete

Kubernetes v1.36: Declarative Validation Graduates to GA

1 Share

In Kubernetes v1.36, Declarative Validation for Kubernetes native types has reached General Availability (GA).

For users, this means more reliable, predictable, and better-documented APIs. By moving to a declarative model, the project also unlocks the future ability to publish validation rules via OpenAPI and integrate with ecosystem tools like Kubebuilder. For contributors and ecosystem developers, this replaces thousands of lines of handwritten validation code with a unified, maintainable framework.

This post covers why this migration was necessary, how the declarative validation framework works, and what new capabilities come with this GA release.

The Motivation: Escaping the "Handwritten" Technical Debt

For years, the validation of Kubernetes native APIs relied almost entirely on handwritten Go code. If a field needed to be bounded by a minimum value, or if two fields needed to be mutually exclusive, developers had to write explicit Go functions to enforce those constraints.

As the Kubernetes API surface expanded, this approach led to several systemic issues:

  1. Technical Debt: The project accumulated roughly 18,000 lines of boilerplate validation code. This code was difficult to maintain, error-prone, and required intense scrutiny during code reviews.
  2. Inconsistency: Without a centralized framework, validation rules were sometimes applied inconsistently across different resources.
  3. Opaque APIs: Handwritten validation logic was difficult to discover or analyze programmatically. This meant clients and tooling couldn't predictably know validation rules without consulting the source code or encountering errors at runtime.

The solution proposed by SIG API Machinery was Declarative Validation: using Interface Definition Language (IDL) tags (specifically +k8s: marker tags) directly within types.go files to define validation rules.

Enter validation-gen

At the core of the declarative validation feature is a new code generator called validation-gen. Just as Kubernetes uses generators for deep copies, conversions, and defaulting, validation-gen parses +k8s: tags and automatically generates the corresponding Go validation functions.

These generated functions are then registered seamlessly with the API scheme. The generator is designed as an extensible framework, allowing developers to plug in new "Validators" by describing the tags they parse and the Go logic they should produce.

A Comprehensive Suite of +k8s: Tags

The declarative validation framework introduces a comprehensive suite of marker tags that provide rich validation capabilities highly optimized for Go types. For a full list of supported tags, check out the official documentation. Here is a catalog of some of the most common tags you will now see in the Kubernetes codebase:

  • Presence: +k8s:optional, +k8s:required
  • Basic Constraints: +k8s:minimum=0, +k8s:maximum=100, +k8s:maxLength=16, +k8s:format=k8s-short-name
  • Collections: +k8s:listType=map, +k8s:listMapKey=type
  • Unions: +k8s:unionMember, +k8s:unionDiscriminator
  • Immutability: +k8s:immutable, +k8s:update=[NoSet, NoModify, NoClear]

Example Usage:

type ReplicationControllerSpec struct {
 // +k8s:optional
 // +k8s:minimum=0
 Replicas *int32 `json:"replicas,omitempty"`
}

By placing these tags directly above the field definitions, the constraints are self-documenting and immediately visible to anyone reading the type definitions.

Advanced Capabilities: "Ambient Ratcheting"

One of the most substantial outcomes of this work is that validation ratcheting is now a standard, ambient part of the API. In the past, if we needed to tighten validation, we had to first add handwritten ratcheting code, wait a release, and then tighten the validation to avoid breaking existing objects.

With declarative validation, this safety mechanism is built-in. If a user updates an existing object, the validation framework compares the incoming object with the oldObject. If a specific field's value is semantically equivalent to its prior state (i.e., the user didn't change it), the new validation rule is bypassed. This "ambient ratcheting" means we can loosen or tighten validation immediately and in the least disruptive way possible.

Scaling API Reviews with kube-api-linter

Reaching GA required absolute confidence in the generated code, but our vision extends beyond just validation. Declarative validation is a key part of a comprehensive approach to making API review easier, more consistent, and highly scalable.

By moving validation rules out of opaque Go functions and into structured markers, we are empowering tools like kube-api-linter. This linter can now statically analyze API types and enforce API conventions automatically, significantly reducing the manual burden on SIG API Machinery reviewers and providing immediate feedback to contributors.

What's next?

With the release of Kubernetes v1.36, Declarative Validation graduates to General Availability (GA). As a stable feature, the associated DeclarativeValidation feature gate is now enabled by default. It has become the primary mechanism for adding new validation rules to Kubernetes native types.

Looking forward, the project is committed to adopting declarative validation even more extensively. This includes migrating the remaining legacy handwritten validation code for established APIs and requiring its use for all new APIs and new fields. This ongoing transition will continue to shrink the codebase's complexity while enhancing the consistency and reliability of the entire Kubernetes API surface.

Beyond the core migration, declarative validation also unlocks an exciting future for the broader ecosystem. Because validation rules are now defined as structured markers rather than opaque Go code, they can be parsed and reflected in the OpenAPI schemas published by the Kubernetes API server. This paves the way for tools like kubectl, client libraries, and IDEs to perform rich client-side validation before a request is ever sent to the cluster. The same declarative framework can also be consumed by ecosystem tools like Kubebuilder, enabling a more consistent developer experience for authors of Custom Resource Definitions (CRDs).

Getting involved

The migration to declarative validation is an ongoing effort. While the framework itself is GA, there is still work to be done migrating older APIs to the new declarative format.

If you are interested in contributing to the core of Kubernetes API Machinery, this is a fantastic place to start. Check out the validation-gen documentation, look for issues tagged with sig/api-machinery, and join the conversation in the #sig-api-machinery and #sig-api-machinery-dev-tools channels on Kubernetes Slack (for an invitation, visit https://slack.k8s.io/). You can also attend the SIG API Machinery meetings to get involved directly.

Acknowledgments

A huge thank you to everyone who helped bring this feature to GA:

And the many others across the Kubernetes community who contributed along the way.

Welcome to the declarative future of Kubernetes validation!

Read the whole story
alvinashcraft
5 hours ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories