Partition Evolution: Change Your Partitioning Without Rewriting Data

Hive-style directory-based partitioning with its three core problems

This is Part 4 of a 15-part Apache Iceberg Masterclass. Part 3 covered metadata-driven performance. This article explains how Iceberg handles the problem that has plagued data lakes for over a decade: what happens when your partition strategy needs to change.

Partitioning determines how data is physically organized in storage, and it is the single most impactful factor for query performance on large tables. Get it right and queries skip 95% of the data. Get it wrong and every query scans everything. The problem is that requirements change, data volumes grow, and the partition strategy that worked last year becomes a bottleneck this year.

Table of Contents

  1. What Are Table Formats and Why Were They Needed?
  2. The Metadata Structure of Current Table Formats
  3. Performance and Apache Iceberg’s Metadata
  4. Technical Deep Dive on Partition Evolution
  5. Technical Deep Dive on Hidden Partitioning
  6. Writing to an Apache Iceberg Table
  7. What Are Lakehouse Catalogs?
  8. Embedded Catalogs: S3 Tables and MinIO AI Stor
  9. How Iceberg Table Storage Degrades Over Time
  10. Maintaining Apache Iceberg Tables
  11. Apache Iceberg Metadata Tables
  12. Using Iceberg with Python and MPP Engines
  13. Streaming Data into Apache Iceberg Tables
  14. Hands-On with Iceberg Using Dremio Cloud
  15. Migrating to Apache Iceberg

The Hive Problem: Partitioning Is Permanent

Hive-style directory-based partitioning with its three core problems

In Hive and other traditional data lake systems, partitions are physical directories. A table partitioned by year and month has a directory structure like:

s3://warehouse/orders/year=2023/month=01/part-0000.parquet
s3://warehouse/orders/year=2023/month=02/part-0000.parquet
...

This design has three fundamental problems:

Changing partitions requires rewriting all data. If a table is partitioned by month and you need daily partitions (because data volume grew and monthly partitions are now too large for efficient queries), you must read every file, re-partition it, and write it back. For a petabyte table, this means a petabyte of compute and I/O, hours of processing, and downtime for consumers.

Users must know the physical layout. Queries must explicitly reference partition columns using the exact partition column names: WHERE year = 2024 AND month = 3. If a user writes WHERE order_date = '2024-03-15', Hive does not recognize that order_date maps to year = 2024, month = 3, and it scans the entire table. This creates a constant burden on users to understand and correctly use the physical layout.

Wrong filters produce silent full scans. There is no error, no warning. The query runs, it just reads every partition. Teams discover the problem only when they notice query times are 50x slower than expected.

How Iceberg Solves This

Iceberg partition evolution showing how old and new partition specs coexist without rewriting data

Iceberg separates the logical partition specification from the physical data layout through two mechanisms: hidden partitioning (covered in Part 5) and partition evolution.

The Partition Spec

Every Iceberg table has a partition spec that defines how source columns map to partition values. The spec does not create directories. Instead, it records partition values as metadata in manifest entries alongside each data file.

-- Create a table partitioned by month
CREATE TABLE orders (
  order_id BIGINT,
  order_date DATE,
  amount DECIMAL(10,2),
  status STRING
) PARTITIONED BY (month(order_date))

When data is written, the engine computes the partition value (month('2024-03-15') = 2024-03) and stores it in the manifest entry for that file. The file itself can live at any path; there is no requirement for a month=2024-03/ directory.

Evolving the Spec

When data volume grows and monthly partitions become too coarse, you change the spec:

ALTER TABLE orders SET PARTITION SPEC (day(order_date))

This is a metadata-only operation. It takes milliseconds. No data is read or rewritten. What happens internally:

  1. The current partition spec (Spec 0: month(order_date)) is preserved in the table’s metadata history.
  2. A new partition spec (Spec 1: day(order_date)) is set as the active spec.
  3. All existing data files retain their Spec 0 partition values in their manifest entries.
  4. All new data written to the table uses Spec 1.

The table now contains files with two different partition specs. This is not a broken state. It is the designed behavior.

How Query Planning Handles Multiple Specs

How engines resolve queries across multiple partition specs by evaluating each independently

When a query filters on order_date, the engine must correctly prune files regardless of which spec they were written under. Here is the process:

SELECT * FROM orders
WHERE order_date BETWEEN '2023-12-01' AND '2024-01-31'

For Spec 0 files (monthly): The engine translates the date range into month values: 2023-12 and 2024-01. It checks manifest entries with Spec 0 partition values and keeps files where the month partition is either 2023-12 or 2024-01. All other months are skipped.

For Spec 1 files (daily): The engine translates the date range into day values: 2024-01-01 through 2024-01-31. It checks manifest entries with Spec 1 partition values and keeps files where the day partition falls within that range. All other days are skipped.

Result: Both old and new files are correctly pruned using their respective specs. The query returns accurate results from files written under different partition strategies, without the user knowing or caring about the spec history.

Real-World Scenarios

Growing From Monthly to Daily

The most common evolution. A startup begins with monthly partitions when data volume is 10 GB/month. Two years later, data volume is 500 GB/month and monthly partitions produce files too large for efficient processing. Evolving to daily partitions makes new data more granular while old data remains accessible.

Adding a Partition Column

A table partitioned only by date starts receiving queries that heavily filter by region. Adding a partition on region (using bucket(16, region)) improves pruning for those queries:

ALTER TABLE orders SET PARTITION SPEC (day(order_date), bucket(16, region))

Old files remain partitioned by date only. New files are partitioned by both date and region. Queries that filter on date work correctly for both old and new files. Queries that filter on region get pruning benefits only for new files.

Removing a Partition Column

If a partition column becomes irrelevant (e.g., a geographic region is no longer used for filtering), you can evolve the spec to remove it. Old files keep their partition values, but new files are no longer organized by that column. Dremio and other engines handle this transparently during query planning.

What About the Old Data?

After a partition evolution, old data continues to work correctly but may have suboptimal organization. The old monthly files are coarser than the new daily files, meaning queries against historical data scan larger files than necessary.

Two options:

  1. Leave it alone. If historical data is queried infrequently, the cost of less-optimal pruning is minimal. This is the zero-effort approach.

  2. Compact old data. Run a compaction job that rewrites old files under the new spec. This produces daily-partitioned files for the historical data too, but requires compute resources. Dremio’s automatic table optimization can handle this for tables managed by Open Catalog.

How Other Formats Handle This

How Other Formats Handle This

Delta Lake’s Liquid Clustering is a different solution to the same problem. Instead of static partitions, it uses adaptive clustering that reorganizes data in the background. The tradeoff: Liquid Clustering requires ongoing background compute, while Iceberg’s partition evolution is a one-time metadata operation with optional follow-up compaction.

Partition evolution is one of the features that makes Iceberg a safe long-term choice. It means the partitioning decision you make today is not permanent. Part 5 covers hidden partitioning, the other half of Iceberg’s partitioning story.

Books to Go Deeper

Free Resources

What Google Play’s I/O 2026 Updates Look Like From a Solo Indie Puzzle Developer

Play Console recommends a custom store listing for the

This is a submission for the Google I/O Writing Challenge.

I chose the Google Play announcements from Google I/O 2026 because they are not abstract for me.

My name is Alla Kuznetsova. I am an indie developer and the person building Marble Sudoku, a mobile puzzle game where classic Sudoku logic is played with colorful marbles instead of numbers. I publish the game under Luma Play. Luma Play is not a large studio and there is no separate product group behind me; it is my small indie label.

So my main problem right now is not only how to make the game better.
But also how to help the right players find it, and then also analyze their behavior on the application page. This article is my experience working with Google AI and updating the Google Play console from Google.

That is why the Google Play updates felt important. Play Shorts, Ask Play, Gemini-assisted store listings, keyword recommendations, and AI-powered insights inside Play Console all point in the same direction: Google Play is becoming less like a static shelf and more like an active discovery system.

And for a solo indie developer, discovery is everything.

The Part That Immediately Made Sense: Custom Listings by Gemini For Real Search Intent

After reading the announcement, I opened Play Console to see whether these updates were already visible in my real workflow.

They were.

In Grow overview, Play Console showed a very specific recommendation for Marble Sudoku: create a custom store listing for users searching for ‘sudoku.’

Play Console recommends a custom store listing for the 'sudoku' search intent

Marble Sudoku by Gemini

Play Console recommends a custom store listing for the ‘sudoku’ search intent.

That was interesting because it was not generic advice like ‘improve your store page.’ It was tied to a concrete search intent.

The console said my app receives significant traffic from searches for ‘sudoku,’ but the conversion rate for that term is lower than the average conversion rate. The suggested action was to create a custom store listing for that term, with a potential conversion-rate impact of +2 percentage points.

Generate suggested descriptions using Gemini

This is exactly the kind of recommendation a solo developer can use.

For a big studio, +2pp might be one line in a growth report. For me, it is a test plan.

Marble Sudoku can be understood in several different ways:

  • classic Sudoku, but visual
  • Sudoku without numbers
  • a relaxing logic puzzle
  • a color puzzle with rules
  • a marble puzzle with a calm feel

Those are different entry points. A Sudoku fan wants to know the logic is real. A casual puzzle player may care more about the look and the feeling. Someone who avoids numbers needs to understand that this is visual, not math-heavy.

A single default store page cannot speak perfectly to all of them.

That is why custom store listings are useful. They let me match the store page to the intent behind the search.

Gemini Makes The Idea Faster, But Not Automatic

From that recommendation, I opened the custom store listing flow. Play Console had already set the target keyword to ‘sudoku.’ The page also showed a Gemini note saying that store listing descriptions had been generated based on that search keyword.

The custom store listing flow is already targeted to the 'sudoku' keyword and references Gemini.

The custom store listing flow is already targeted to the ‘sudoku’ keyword and references Gemini.

That is where the I/O announcement started to feel real.

The flow connected three things that are usually separate:

  1. Google Play sees a search opportunity.
  2. It suggests a custom listing.
  3. Gemini helps draft listing content.

For one developer, this is powerful. I do not have an ASO department. I do not have a dedicated person writing twenty store-page variants. Usually, every experiment costs time and focus.

If Gemini can turn a keyword opportunity into a first draft, that lowers the cost of testing.

And in my session, the generation flow also showed the rough edge: the description could not be generated and Play Console asked me to try again later.

Gemini generation failed in the custom listing flow.

That did not make the feature useless. It made it feel real.

The announcement says AI can help with store listing creation. The developer experience says: yes, but I still need to review, retry, edit, and understand what I am publishing.

That is probably the right relationship with AI in ASO. Not ‘AI replaces positioning,’ but ‘AI helps create more testable versions of positioning.’

Play Shorts Sounds Perfect For My Game, But The Upload Path Confused Me

The I/O update I was most excited about was Play Shorts.

For Marble Sudoku, short vertical video makes immediate sense. The game is easier to understand in motion than in a screenshot.
But when I checked the YouTube videos section in Play Console, the workflow became confusing.

The YouTube videos section says YouTube Shorts and live videos will not be displayed.

The YouTube videos section says YouTube Shorts and live videos will not be displayed.

The requirements are clear, but the workflow is not. Play Shorts sounds like a portrait short-form video format, while the current YouTube video section says that YouTube Shorts will not be displayed. I understand the idea, but I still could not figure out how to upload the right kind of video.

The connected playlist shows zero videos available for Play.

The connected playlist shows zero videos available for Play.

That is useful feedback, but it still leaves me guessing. Video could be one of the best discovery surfaces for Marble Sudoku, because the game is easier to understand in motion. But for Play Shorts to be useful to indie developers, the upload path needs to be clearer.

Ask Play Changes What Search Means

Another update that stood out to me was Ask Play, Google’s AI-powered discovery experience.

Players usually do not search for games they do not know exist. Almost nobody wakes up and searches for “Marble Sudoku.” They search for intent: a relaxing puzzle, Sudoku without numbers, a beautiful logic game, something calm but still challenging.

That is why Ask Play matters. If Google Play starts understanding what players mean, not only what they type, niche games have a better chance to be discovered.

But it also raises the quality bar. My store listing needs to explain the game very clearly: this is Sudoku logic, with colors and marbles instead of numbers. It is calm, but still a real puzzle. That is not just marketing copy anymore. It is discovery input.

What I Wish Play Console Did Next

Because I already use Gemini API outside Play Console, I can imagine a more complete workflow inside the console itself.

For events, I would love an AI assistant that helps create the full package: cover image, tagline, description, translations, policy checks, and tone variants.

For new builds, I would love AI-assisted release notes. Play Console could ask what changed, or read a structured changelog, and suggest short user-facing update text.

For video, I would love a clearer eligibility explainer. If a playlist has 0 videos available for Play, tell me exactly why: Short, monetized, private, copyrighted, wrong format, or still processing.

These may sound like small workflow improvements, but for a solo developer they matter. AI does not replace strategy for me. It removes repetitive friction around strategy.

My Practical Plan After I/O

After checking the announcements and Play Console, I see these updates as one workflow.

First, I would create a custom store listing for “sudoku” that speaks directly to Sudoku players.

Second, I would create another listing for the “relaxing puzzle” angle.

Third, I would prepare short vertical gameplay videos, even if the Play Shorts path is not fully clear yet.

Fourth, I would keep using Gemini API for event and social creative, but connect those experiments back to Play Console data.

The goal is not to create more content. The goal is to better match player intent with the first thing they see.

My Main Takeaway

My impression after Google I/O 2026 is optimistic, but not blindly optimistic.

Google Play is moving toward active discovery: search intent, AI recommendations, short videos, custom listings, and reporting working together.

For large studios, that gives scale. For solo developers, it gives a chance.

and for solo developers, that chance often looks like automation: one person being able to create the kind of consistent, colorful, multi-channel presence that normally requires a marketing department.

The tools still need clarity. Gemini-assisted listings are promising, but generation can fail. Play Shorts sounds perfect for mobile games, but the relationship between Play Shorts, YouTube videos, preview videos, and unsupported YouTube Shorts is still confusing.

So my takeaway is this:

Google Play’s new discovery tools do not remove the need for ASO thinking. They make ASO more testable.

and for Marble Sudoku, that is enough to be exciting.

I Already Use Gemini, Just Outside Play Console

One reason these announcements caught my attention is that I already use the Gemini API in my own workflow.

I use it to generate visual ideas and covers for Google Play events, social posts, and community updates. I also run a DEV blog for Luma Play, where I share Marble Sudoku updates, screenshots, leaderboard posts, and cozy puzzle moments with my game character: dev.to/lumaplay.

Thanks to Gemini, I can fill my public channels with colorful content instead of leaving them empty between releases. I use it as part of a publishing loop across my site, DEV, X, Micro.blog, Tumblr, Bluesky, Reddit, LinkedIn and other places where players or developers may discover the game.

Micro.blog visual by Gemini:

Micro.blog publishing for Marble Sudoku

Reddit visual by Gemini:

visual profile for Marble Sudoku

This is not AI for novelty. It is AI because a solo developer has repeating creative work every week: event images, post copy, update notes, short captions, store experiments, and localization drafts.

For an indie developer, this kind of automation is not a luxury. It is one of the only realistic ways to compete with media giants that have teams for trailers, social posts, store copy, localization, community updates, and paid creative testing.

So when I see Gemini appear inside Play Console, I do not see it as just a nice demo. I see something that could save hours of repetitive work.

For me, the next natural step would be an AI event workflow inside Play Console: choose the event type, describe what changed, and get a draft package with a cover image, tagline, event description, translations, and a policy checklist.

The same could happen when uploading a new build. Play Console could suggest user-facing release notes from the developer’s changelog.

That would be useful because, to compete with media giants, I need to spend less time rebuilding the same marketing workflow manually and more time improving the game.

Disclosure

I used AI assistance to organize and edit this post. The product experience, critique, screenshots, examples, and final opinions come from my real work on Marble Sudoku and my own review of Play Console.

Forgetting the Myth of “Ease of Integration” When Selling Digital Products with Bitcoin

The Problem We Were Actually Solving

We had a digital product that we were eager to sell to customers worldwide. However, our chosen payment gateway refused to support international transactions, citing anti-money laundering (AML) and know-your-customer (KYC) regulations. This was a problem not just for us, but for many other digital product creators who rely on global sales to sustain their businesses. The question was: how could we accept Bitcoin payments without being bound by the limitations of traditional payment gateways?

What We Tried First (And Why It Failed)

In our early attempts to integrate Bitcoin payments, we tried using popular libraries like Stripe’s Bitcoin integration and a few other open-source libraries. While they offered a convenient API, we soon realized that they relied on the same flawed underlying architecture that the payment gateways used – namely, the need for intermediaries to process transactions. As a result, we encountered issues with scalability, security, and processing fees that outweighed the convenience.

The Architecture Decision

After hitting a brick wall with the popular libraries, we decided to take a more radical approach. We chose to implement our own Bitcoin payment processing system from scratch, using Node.js and a custom-built payment processor. This decision was motivated by our desire to have complete control over the payment flow, as well as the need to ensure scalability, security, and low transaction costs. We also opted for a decentralized approach, using a combination of off-chain and on-chain transactions to facilitate fast and secure payments.

What The Numbers Said After

After deploying our custom payment processor, we noticed a significant improvement in our transaction processing times, with average latency dropping from 30 seconds to just 3 seconds. Allocation counts for memory usage also decreased by 40%, allowing us to process more transactions without sacrificing performance. Meanwhile, security improved substantially, thanks to our custom-built processor’s ability to verify transactions without relying on intermediaries. Of course, there were still some unexpected issues, like the one time we had to deal with a rogue transaction that tried to drain our wallet – but that’s a story for another time.

What I Would Do Differently

Looking back on our experience, I would advise against trying to integrate Bitcoin payments using the popular libraries. While they may seem convenient, they’re often built on shaky ground and will inevitably lead to scalability issues and security concerns. Instead, I would recommend taking a more radical approach, like we did, and building a custom payment processor from scratch. It may take time, effort, and expertise, but the benefits far outweigh the costs. After all, when it comes to selling digital products, the last thing you want is to be held hostage by a payment system that’s more interested in profits than in supporting your business.

Samsung Galaxy S27 Pro için yeni kamera detayları paylaşıldı

Samsung Galaxy S27 Pro

Samsung Galaxy S27 Pro2027’nin başında pazara sunulacak Samsung Galaxy S27 serisinde “Galaxy S27, Galaxy S27+, Galaxy S27 Pro ve Galaxy S27 Ultra” modellerinin yer alacağı aktarılıyor. Dün 6,47 inç boyutunda 120 Hz OLED ekranla donatılacağı ve entegre S Pen ile gelmeyeceği söylenen Galaxy S27 Pro, Çinli kaynak Ice Universe’e göre Galaxy S27 Ultra ile aynı geniş açı ve ultra geniş açı kameraları (her iki kamerada da yeni sensörler kullanılacağı belirtiliyor) taşıyacak. İki telefon arasında görüntüleme açısından fark yaratan noktanın ise “telefoto” kamera olacağı söyleniyor. Henüz net bir açıklama yok ancak Galaxy S27 Pro’da Galaxy S27 Ultra’ya kıyasla daha az optik yakınlaştırma/zoom sunulabilir.

Samsung Galaxy S27 Pro için yeni kamera detayları paylaşıldı yazısı ilk olarak Teknoloji haberleri – LOG üzerinde yayınlandı.

My 4-Step Regex Debugging Workflow (That Actually Saves Time)

I’ve written hundreds of regular expressions over the years, and I still get them wrong on the first try. Here’s the debugging workflow I’ve settled on that consistently gets me from “why isn’t this matching” to “oh, that’s why” in under a minute.

Step 1: Isolate One Pattern at a Time

The most common mistake I see (and make) is testing a monster regex all at once. If you’re validating an email with a complex pattern and it’s not working, break it down: test just the local part, then the domain, then combine them. This alone has saved me hours of staring at a wall of backslashes and brackets.

Step 2: Check Your Quantifier Greediness

This one bites everyone. A pattern like <.*> against "<div>hello</div>" matches the entire string, not just the opening tag. The fix: add ? to make it lazy — <.*?>. I can’t count how many “my regex is broken” posts on Stack Overflow boil down to this. Whenever a pattern matches too much, my first instinct is to check for greedy quantifiers.

Step 3: Toggle the Flags

Flags change everything. hello matches “hello” — but add the i flag and hello now matches “HELLO”, “Hello”, and “hElLo”. The ^ anchor matches the start of a string by default, but with the m (multiline) flag, it matches the start of each line. I often toggle the g (global) flag off during debugging to focus on the first match, then turn it back on to check all occurrences.

Step 4: Use a Real-Time Tester

This is the game-changer. Rather than write-test-rewrite cycles in my code editor, I use a live regex tester that highlights matches as I type. Seeing matches highlighted instantly — and seeing capture groups broken out individually — makes pattern bugs obvious in seconds. I use CodeToolbox Regex Tester because it runs entirely in the browser (no data uploads) and includes presets for common patterns like email, URL, and IPv4 to use as starting points.

The complete workflow takes less than a minute once it becomes habit: isolate → check greediness → toggle flags → test live. These four steps have saved me more debugging time than any regex cheatsheet ever has.

What’s your go-to regex debugging trick? I’d love to hear what works for you in the comments.

Stop Scraping Betting Sites: How to Build a Real-Time Sports Tracker in Python

If you have ever tried to build a sports dashboard, an arbitrage scanner, or an automated trading bot, you already know the painful truth: scraping bookmaker data is a nightmare.

Sites like Bet365, FanDuel, and DraftKings have aggressive anti-bot protection (hello, Cloudflare). Even if you bypass it, they constantly change their HTML structure, breaking your scrapers every few weeks. Plus, managing custom WebSocket connections for live in-play odds is an infrastructure money pit.

In this quick guide, we’ll build a real-time sports odds tracker in Python without writing a single line of scraper code.

To do this, we’ll use PulseScore, a real-time sports odds API that aggregates multiple bookmakers into a single, unified JSON shape. They have a completely free tier (no credit card required), which is perfect for this project.

🛠️ The Tech Stack

  • Python 3.x
  • Requests library (for polling pre-match data)
  • WebSockets library (for ultra-low latency live updates)

First, make sure you have the required packages installed:

pip install requests websockets

1️⃣ Fetching Pre-Match Odds (REST API)

One of the biggest headaches of multi-bookie apps is normalizing data. Every bookmaker names their markets and sports differently. PulseScore solves this by serving the same JSON structure whether you fetch from Bet365, DraftKings, or Bwin.

Here is how to fetch upcoming tennis or soccer matches using their clean REST endpoints:

import requests

# Get your free API key from [https://pulsescore.net](https://pulsescore.net)
API_KEY = "YOUR_FREE_SECRET_HEADER_KEY"
URL = "[https://api.pulsescore.net/v1/odds/bet365](https://api.pulsescore.net/v1/odds/bet365)" # You can easily swap /bet365 for /fanduel or /bwin

headers = {
    "X-Secret": API_KEY,
    "Accept": "application/json"
}

response = requests.get(URL, headers=headers)

if response.status_code == 200:
    data = response.json()
    # Let's print out the first few upcoming events
    for event in data.get("events", [])[:5]:
        print(f"Match: {event['home_team']} vs {event['away_team']}")
        print(f"Start Time: {event['start_time']}")
        print(f"1X2 Odds: {event['markets'].get('1x2')}n")
else:
    print(f"Error: {response.status_code}, {response.text}")

2️⃣ Streaming Live In-Play Odds (WebSockets)

For live betting or real-time trading, a 10-second delay is unacceptable. WebSockets allow you to stream low-latency data straight into your application.

Here is how you can listen to live match updates with a 1-2 second refresh rate:

import asyncio
import websockets
import json

API_KEY = "YOUR_FREE_SECRET_HEADER_KEY"
WS_URL = f"wss://ws.pulsescore.net/live?secret={API_KEY}"

async def stream_live_sports():
    async with websockets.connect(WS_URL) as websocket:
        print("Successfully connected to PulseScore live stream...")

        while True:
            try:
                message = await websocket.recv()
                event_data = json.loads(message)

                # Print real-time shifts in lines/odds
                print(f"Live Update: {event_data['home_team']} vs {event_data['away_team']}")
                print(f"Current Score: {event_data['score']}")
                print(f"Live In-Play Odds: {event_data['live_markets']}n")

            except websockets.ConnectionClosed:
                print("Connection closed, reconnecting...")
                break

asyncio.run(stream_live_sports())

💡 Why This Beats Custom Scrapers

  1. Unified JSON Shape: You can swap /bet365 for /fanduel, /bwin, or /ps3838 in your code, and the payload format remains identical. No need to map data structures manually.

  2. No IP Bans: You don’t have to manage proxy rotation or deal with headless browsers.

  3. Low Latency: Live events update every 1 second, keeping your data ahead of the curve.

Conclusion
Building sports tools should be about creating smart algorithms and beautiful dashboards, not fighting website layouts and Cloudflare blocks.eo

If you want to try this out yourself, head over to pulsescore.net, spin up a free plan (gives you 500 requests/mo right away), and test the payloads with the scripts above.

What are you currently building with sports data? Let me know in the comments below! ⚽🏀🎾

Civic Identity and Responsibility in Modern Democracy

Democracy depends on more than constitutions, elections, and political institutions. While laws and systems provide structure, the true strength of democratic society comes from citizens who actively participate in public life and contribute to the well-being of their communities. Civic identity and civic responsibility remain essential because they encourage individuals to move beyond passive observation and become engaged participants in shaping society.

Perspectives associated with Johnathan Bartee often emphasize that democratic values are strengthened through participation, service, and community engagement. Citizens who understand the importance of civic responsibility help create more resilient communities, reinforce public trust, and contribute to the long-term stability of democratic institutions.

Understanding Civic Identity

Civic identity refers to the connection individuals feel toward their society and democratic system. It reflects the understanding that citizenship includes both rights and responsibilities. In democratic societies, people are encouraged not only to benefit from freedom and opportunity but also to contribute positively to the broader community.

A strong civic identity develops through education, family influence, cultural traditions, and real-world experiences. Schools teach civic principles, communities encourage participation, and families often model values related to responsibility and public engagement.

Citizens who develop a strong civic identity are typically more likely to vote, volunteer, participate in community discussions, and support local initiatives. These forms of participation strengthen democratic culture because they encourage cooperation, accountability, and shared responsibility.

Democracy thrives when citizens recognize that their actions influence the health of their communities and institutions. Participation creates stronger social connections and helps reinforce the belief that collective effort can lead to meaningful progress.

Civic Responsibility Beyond Politics

Many people think of civic responsibility only in terms of voting or political participation, but civic engagement extends far beyond elections. Civic responsibility also includes volunteerism, community service, mentorship, and participation in projects that improve public life.

These activities strengthen communities because they encourage citizens to work together toward common goals. Volunteers who support food programs, educational initiatives, environmental projects, or public health campaigns contribute directly to social well-being while also building stronger relationships within their communities.

Service also creates opportunities for individuals from different backgrounds to collaborate. In diverse democratic societies, people often hold different political views, cultural traditions, and personal experiences. Community engagement provides common ground where individuals can cooperate despite these differences.

This collaborative environment strengthens democratic culture because it encourages empathy, understanding, and trust. Citizens who work together toward practical goals are more likely to appreciate shared interests and recognize the importance of cooperation.

Observations associated with Johnathan Bartee often highlight the idea that civic participation creates stronger communities by encouraging citizens to become active contributors rather than passive observers.

Historical Importance of Civic Participation

The relationship between democracy and civic responsibility has deep historical roots. Democratic societies throughout history have relied on active citizen participation to maintain stability and legitimacy.

In early democratic communities, citizens were expected to contribute to public life through governance, local projects, and collective decision-making. Participation was considered essential because democratic systems depended on citizens who were willing to support the broader community.

The American democratic tradition also placed strong emphasis on civic participation. Local organizations, town meetings, volunteer associations, and community initiatives became important parts of public life. Citizens who engaged in these activities helped strengthen social trust and reinforce democratic values.

Historical periods of national challenge often demonstrated the importance of shared responsibility and collective action. Communities came together during economic hardships, social transitions, and national emergencies to support one another and preserve stability.

These historical examples continue to offer important lessons today. Modern democratic societies still rely on active citizenship and civic engagement to address social challenges and maintain public trust.

Volunteerism and Community Engagement

Volunteerism remains one of the clearest expressions of civic responsibility in modern society. Millions of people contribute their time, skills, and resources to causes that support education, healthcare, environmental protection, and social services.

Community engagement allows citizens to make tangible contributions while also strengthening social connections. Volunteers often gain a better understanding of local challenges and develop stronger relationships with the people around them.

Service projects also encourage leadership development. Individuals who participate in community initiatives frequently gain experience in communication, organization, collaboration, and problem-solving. These skills benefit both the individual and society as a whole.

Volunteerism also reinforces the democratic principle that individuals can create positive change through collective effort. Citizens who work together to address challenges help create stronger and more resilient communities.

Perspectives connected to Johnathan Bartee often emphasize that volunteerism remains a powerful way to strengthen democratic participation and encourage social responsibility.

Civic Education and Future Generations

The future of democracy depends heavily on how younger generations understand civic identity and responsibility. Civic education helps students learn about democratic systems, public institutions, and the importance of participation.

However, effective civic education involves more than classroom instruction. Young people also benefit from practical experiences such as volunteer programs, community projects, and service-learning initiatives.

These experiences teach students how their actions can influence society while also helping them develop empathy, leadership abilities, and critical thinking skills. Service-learning programs are especially valuable because they combine academic knowledge with real-world engagement.

Families and communities also play an important role in shaping civic values. Young people who see examples of volunteerism and public participation are more likely to adopt those behaviors themselves.

By encouraging civic engagement early in life, societies help cultivate future citizens who are prepared to contribute meaningfully to democratic institutions and community life.

Civic Responsibility in the Digital Era

Technology has transformed how citizens participate in public life. Social media platforms and digital communication tools provide new opportunities for civic engagement, awareness campaigns, and community organizing.

Digital platforms make it easier for people to support charitable causes, organize volunteer efforts, and raise awareness about social issues. These tools can strengthen participation when used responsibly.

At the same time, the digital environment also creates challenges related to misinformation, polarization, and online hostility. Civic responsibility in the digital era requires individuals to think critically, verify information, and engage respectfully with others.

Responsible digital citizenship involves using technology in ways that support constructive dialogue and strengthen communities rather than contribute to division.

Communities that encourage thoughtful digital participation are often better equipped to maintain healthy democratic discussion and public trust.

Democracy Through Participation

Ultimately, democracy depends on citizens who are willing to participate actively in public life. Civic identity and civic responsibility provide the foundation for stronger communities, healthier institutions, and more resilient democratic societies.

Volunteerism, service, leadership, and public engagement all reinforce democratic values by encouraging cooperation, accountability, and shared responsibility. Citizens who contribute positively to their communities help create environments where democracy can continue to thrive.

Perspectives associated with Johnathan Bartee continue to emphasize that democracy is not self-sustaining. It requires active citizens who recognize that freedom and participation are closely connected.

As societies continue to evolve, civic engagement remains one of the most important ways individuals can strengthen democracy and contribute to a more connected and responsible society.

OLTP vs OLAP

Introduction

It’s the favorite time of the year, Christmas. It’s Christmas eve night and you remember that you haven’t gotten your special person a gift yet. What do you do? hurriedly you get you phone and go straight to your favorite online shopping platform. You search for the product you want and you find it, only one is remaining in stock ‘I am lucky’ you sigh in relief and click on the buy now button. Immediately you click on the button, a popup appears saying ‘Out of Stock’, I guess you were not so lucky after all. But how did this happen? That is where Online Transactional Processing (OLTP) comes into play. Behind the scenes there was a system that made sure that you were able to see that your product has only one item left in stock and milliseconds later tell you that it is out of stock and at the same time sell the product to the other person who beat you to it. Next time buy your gifts early.
January is hear and the festivities are over. As data analyst working for an online shopping platform, your organization wants to go back in time ( 5 years), know what products sell best during the Christmas season, what regions have the highest sales, total revenues generated and customer trends over the period. How will these be possible? That is where Online Analytical Processing (OLAP) comes into play. With OLAP, you will have a system optimized to enalble you as an analyst perform complex queries and data analysis on these vast amount of data recorded over the past five years.

What is OLTP?

Online Transaction Processing (OLTP) is a type of database system that is designed to manage and process large numbers of short, fast and real-time transaction such as banking operations, online shopping, ATM withdrawals and point-of-sale purchases.

For example: When using an online shopping platform, you select the item you want to buy, add it to a shopping cart, enter your payment details and then click on the ‘buy now’ button. But what exactly happens? behind the system lies an online transactional processing system that verifies your customer account, checks the product inventory to make sure that the product you intend to buy is in stock, records the order, process the payment, updates the stock levels, generates an invoice and sends an order confirmation. Each of these actions must happen quickly and accurately within seconds and milliseconds while thousands if not millions of other users are also shopping at the same time. The OLTP database ensures that the transactions are completed reliably and preventing a situation where a product is sold twice while only one time was in stock.

What is OLAP?

Online Analytical Processing (OLAP) is a type of database system designed for analyzing large amounts of historical and business data to support decision-making, reporting, forecasting and business intelligence. They are optimized to handle complex queries and data analysis across multiple dimensions such as time, region, product or customers.

For example: A a supermarket chain with multiple stores across the country collects all its transactions data including what was sold, when, where and at what price and stores it a data warehouse. Managers and analysts can perform analysis on these data and gather insights such as; comparing total sales across different regions, finding out which products sell the most during weekends vs weekdays and perhaps identifying the best and least performing stores.

So what is the difference between OLTP and OLAP?
OLTP is optimized for executing online database transactions helping you to reliably and efficiently collect and store high volumes of transactional data while OLAP on the other hand is optimized for complex data analysis aiding you in analyzing the stored data and get insights that lead to business decisions and strategies.

Key differences between OLTP and OLAP

The primary purpose of Online Transactional Processing is to process (OLTP) is to process database transactions while Online Analytical Processing (OLAP) is to analyze aggregated data.

1. Data formatting

  • OLPT systems are unidimensional and focus on one data aspect. They use relational databases to organize data into tables with each raw representing an entity instance and each column representing an entity attribute.
  • OLAP systems store data in multidimensional data models so you can view data from different angles. OLAP databases store data in a cube format where each dimension represents a different data attribute and each of the cell in the cube represents a value or measure for the intersection of the dimensions.

2. Data architecture

  • OLTP database architecture prioritizes data write operations. It is optimized for write-heavy workloads and can update high-frequency, high-volume transactional data without compromising data integrity.
  • OLAP database architecture prioritizes data read over data write operations allowing analysts to perform complex queries on large volumes of data. Also, availability is low concern as the primary use case is analytics.

3. Performance

  • OLTP processing times are in milliseconds or less. OLPT databases manage databases updates in realtime. Updates are fast, short and triggered by you or your users. Stream processing is often used over batch processing.
  • With OLAP, processing times can vary from minutes to hours depending on the type and volume of data being analyzed. To update OLAP databases, you periodically process data in large batches then upload the batch to the system all at once. Data update frequency also varies between systems from daily, weekly or even monthly.

4. Requirements

  • OLTP requires high amounts of compute power and storage space. This is because of their nature of performing high amounts of transactions and at high speeds. They also store high volumes of data thus requiring a good amount of storage space ranging to gigabytes(GB). OLPT database can be cleared once the data has been moved to a data warehouse.
  • OLAP systems act like centralized data storage pulling data from data warehouses, relational databases and other systems. Storage requirements measure from terabytes(TB) to petabytes(PB). Data reads can also be compute intensive thus requiring high performing servers.

When to use OLPT vs OLAP

pick OLTP when your application processes individual transactions that need immediate consistency and fast response times. For example an e-commerce application or banking application that performs high volumes of transactions, has strict requirements where it cannot tolerate temporary inconsistencies and has low-latency point lookups such as user authentications and balance checks.

Choose OLAP when you need to analyze historical data through complex queries that aggregate millions of rows. For example huge multinational organizations that need to perform complex aggregations at scale, analyze historical trend analysis and has dashboard need for visualization and reporting needs for management.

Can OLPT and OLAP be used together?

The answer is yes. OLTP and OLAP systems are often used together in modern data systems where OLTP handles day-to-day business operations while OLAP supports analysis and decision making. For example, a business using OLTP to record customer transactional data in real time an using technologies such as change data capture (CDC) and tools such as Apache Kafka and Debezium they can stream database changes to OLAP systems.

Conclusion

OLTP and OLAP serve complementary but distinct roles in modern data-driven systems. OLTP is optimized for speed,accuracy and reliability while handling real-time transactional operations while OLAP is optimized for complex analysis enabling organizations to explore historical data and gather insights.

“Online Transactional Processing (OLTP) enables you to purchase products online and bank reliably while Online Analytical Processing (OLAP) enables the online shopping platform and banking institutions to perform analysis of you other millions of customers data and provide better services.”

Are binaries really executable code ?

What is the difference between a binary file and an executable( machine code )?

so this questions has been in my head for a long time and they are almost used interchangabely right but are they so lets find out ..and i want to learn this from the very first principle . so in order to understand this we would use C and cause its simple and fun to mess around with without unnecessary abstractions . so first we would need to get some fundamentals clear that i am rusty on .

to answer this we need to understand how c actually turns your code into something you can run — and that journey is where the answer lives.

char text[100] = "hello";
char other[100] = "hello";
// this is a simple declaration in c for declaring 2 array of char 
// This compares addresses, NOT content - always false
if (text == other) {
 printf("match"); // never runs
}
// This compares the actual characters - works correctly
if (strcmp(text, other) == 0) {
 printf("match"); // runs!
}

strcmp? what is the full form ? and what about strncpy? i kind of wonder what these are made of ? ← FIXED: added strncpy mention here

ok lets try to look at the source code of strncpy i hopes its called .. ← FIXED: says strncpy now

/* Copyright © 1991–2018 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, see http://www.gnu.org/licenses/. */

please dont sue me or anything i know i dont own any of this i am just an idiot that is it and if you do please tell me before so i can delete it

#include <string.h>
#undef strncpy
#ifndef STRNCPY
 #define STRNCPY strncpy
#endif

what is this ? what is a preprocessor directive anyways ? what is it telling the compiler ?


char *
STRNCPY (char *s1, const char *s2, size_t n)
{
 size_t size = __strnlen (s2, n);
 if (size != n)
 memset (s1 + size, '', n - size);
 return memcpy (s1, s2, size);
}
libc_hidden_builtin_def (strncpy)

so here is the first thing that is bugging me fom this example .what the hell is a preprocessor directive ? so what is a preprocessor ? a preprocessor is like a butler that formats and changes the code into a more “refined” version of the code that is then fed to the compiler . so that would mean that if the preprocessor is not related to the compilation of the language then its not in the compilation of the code . making this # stuff something that would help the compiler do its job somehow . so here is what happens in c during the compilation and before the compilation –

the preprocessor just copy pastes all the headers that you have imported in the code in the main c program .. i am not even kidding its just a big copy and paste if you have used a library like #include or something its gonna literally copy and paste all the functions that are in that header file into your main program . to see this with your own eyes write a program and import all the headers that you want and then just write this in the terminal

gcc -E main.c

i am assuming that your file name is main.c alright dont be dumb like me as to copy and paste the entire line for your file name and then thinking why doesnt it work this -E only activates the preprocessor and hence you can see the copy and paste happening with your own eyes . then whatever the remaining of the code is there usually in one file is gonna be fed to the compiler for turning that into machine code .

one thing really caught my eye is that c doesnt allow you to have two functions definitions even though they are literally the same in terms of definition the c compiler will throw an error as soon as it comes across . now this might look harmless cause well we wont define a functions twice right but it has stuff that can be dangerous in the grand scheme of things . the preprocessor will copy the functions right it doesnt care if they are the same or not its just does copy and paste and some of the other simmilar stuff that we will talk about in a sec . so before we talk about it i want you to answer a question for me — Q. I just said that the compiler wont let you define something twice right? and the implications of that is that lets say that i use a popular header in the c program right something like stdio.h right and lets say that another header file that i am using in my code also have that … then shouldnt it give out an error ? lets try it — ← FIXED: “declare” → “define”

this is the main.c right and here i am importing some of the header file without particularly using them but that is not the point alright-

#include <stdio.h>
#include <stdlib.h>
#include "math_my.h"
int main(){
printf("%dn",add(4,6));
// the add function is defined in the math_my.h 
//this is defined below ..
}

this is the math_my.h

#include <stdio.h>
int add(int a , int b ){
 return a+b;
}

now notice something that i told you all before and that is that the is included twice and yet the compiler wont give out any errors ? why is that ? and this question is really interesting … ← FIXED: “declared” → “included”

Learn about Medium’s values
here look at the compilation message

avirals554@Avirals-MacBook-Air c-term % gcc explore.c -o explore
avirals554@Avirals-MacBook-Air c-term % ./explore
10
42
look it compiled ? but why is that ? how ? i mean it shouldnt right ? but why did it ? even though we are literally using the stdio.h twice ? hmmm… ok lets read the stdio.h itself alright

avirals554@Avirals-MacBook-Air c-term % cat /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/stdio.h
i just hope i dont get copyright violation or something . that is why i have added this so the important thing in all of this is this like #include <_stdio.h> so this is importing another header file ? and what are these stuff ??


#ifdef _USE_EXTENDED_LOCALES_
#include <xlocale/_stdio.h>
#endif /* _USE_EXTENDED_LOCALES_ */

hmm iteresting .. lets read the _stdio.h file right … ok apart from the copyright stuff this is what i am able to extract from this file –

#ifndef _STDIO_H_
#define _STDIO_H_
void clearerr(FILE *);
int fclose(FILE *);
int feof(FILE *);
int fgetc(FILE *);
int fprintf(FILE * __restrict, const char * __restrict, …);
int fputc(int, FILE *);
int scanf(const char * __restrict, …);
int printf(const char * __restrict, …);
#include <_types.h>
#include <sys/_types/_va_list.h>
#include <sys/_types/_size_t.h>
#if __DARWIN_C_LEVEL >= 200809L
 ssize_t getline(…);
#endif

alright i get some of it 2nd one is the declaration of the functions that it has right but not the definitions themselves ? so where is it getting the actual definitions from ? i mean if the preprocessor is just gonna copy and paste this then … where does it get the definitions of these functions ? …

so here is the fun thing . the compiler actually compiles this as it is like this into the machine code meaning the compiler doesnt get the definition of those functions at all … so you might ask wait …. then how does the code works at all just from the declarations of those functions ?? doesnt it needs to get those definitions ? it does it AFTER the compilation.. through a little thing called a linker .. which finds those definitions and in this there is a file that i found named this — libSystem.dylib and i am not sure what this library has accurately but my best guess is that its must have those definitions but as its not in the .h format they are stored in some other format . now here is what bugs me if the code is already compiled then it cannot be in the c code right ? i mean it must be in the machine code cause the compilations is already done . now i need to find the step just before the linker and ask the gcc to show my that only , so the process is like this –

code → preprocessor → compiler → linker → executable ← FIXED: “machine_code” → “executable”

alright we know what is (2) right ? from this command that we talked about above — gcc -E main.c

alright so this is a code that is what the compiler sees right and as i said about the same name problem so i am guessing that the compiler dont have a problem with declarations being done twice — and that is the key insight. stdio.h only contains declarations (just the list of functions without the actual code), and you can declare something as many times as you want. What you cant do is define it twice — the full function with the curly braces and the code inside. Thats what would cause an error. ← FIXED: clarified the declaration vs definition point

ok but to know that i need to see what the compiler produces right ? so i looked it up and got this –

gcc -c main.c

this creates a .o file with the same name so we get a main.o file and so if i try to read it via cat i am getting garbage meaning its a machine code probably ,so we will use this –

objdump -d explore.o

this objdump thing is just something that can apperently read BINARIES, and if you see the topic of this blog i am asking the difference between binaries and executable so here we are .. this .o file is an incomplete binary. meaning that it still does the thing was in the code that the preprocessor gave it but doesnt really have the definitions yet . so we must read it the .o file i mean .. so after running the command i get this –


objdump -d explore.o
explore.o: file format mach-o arm64
Disassembly of section __TEXT,__text:
0000000000000000 <ltmp0>:
 0: d10043ff sub sp, sp, #0x10
 4: b9000fe0 str w0, [sp, #0xc]
 8: b9000be1 str w1, [sp, #0x8]
 c: b9400fe8 ldr w8, [sp, #0xc]
 10: b9400be9 ldr w9, [sp, #0x8]
 14: 0b090100 add w0, w8, w9
 18: 910043ff add sp, sp, #0x10
 1c: d65f03c0 ret
0000000000000020 <_square>:
 20: d10083ff sub sp, sp, #0x20
 24: a9017bfd stp x29, x30, [sp, #0x10]
 28: 910043fd add x29, sp, #0x10
 2c: b81fc3a0 stur w0, [x29, #-0x4]
 30: b85fc3a0 ldur w0, [x29, #-0x4]
 34: b85fc3a1 ldur w1, [x29, #-0x4]
 38: 94000000 bl 0x38 <_square+0x18>
 3c: b9000be0 str w0, [sp, #0x8]
 40: b9400be8 ldr w8, [sp, #0x8]
 44: 11008908 add w8, w8, #0x22
 48: b9000be8 str w8, [sp, #0x8]
 4c: b9400be0 ldr w0, [sp, #0x8]
 50: a9417bfd ldp x29, x30, [sp, #0x10]
 54: 910083ff add sp, sp, #0x20
 58: d65f03c0 ret
000000000000005c <_main>:
 5c: d10103ff sub sp, sp, #0x40
 60: a9037bfd stp x29, x30, [sp, #0x30]
 64: 9100c3fd add x29, sp, #0x30
 68: 52800008 mov w8, #0x0 ; =0
 6c: b81ec3a8 stur w8, [x29, #-0x14]
 70: b81fc3bf stur wzr, [x29, #-0x4]
 74: b81f83a0 stur w0, [x29, #-0x8]
 78: f81f03a1 stur x1, [x29, #-0x10]
 7c: 52800080 mov w0, #0x4 ; =4
 80: b9000fe0 str w0, [sp, #0xc]
 84: 528000c1 mov w1, #0x6 ; =6
 88: 94000000 bl 0x88 <_main+0x2c>
 8c: 910003e9 mov x9, sp
 90: aa0003e8 mov x8, x0
 94: f9000128 str x8, [x9]
 98: 90000000 adrp x0, 0x0 <ltmp0>
 9c: 91000000 add x0, x0, #0x0
 a0: f9000be0 str x0, [sp, #0x10]
 a4: 94000000 bl 0xa4 <_main+0x48>
 a8: b9400fe0 ldr w0, [sp, #0xc]
 ac: 94000000 bl 0xac <_main+0x50>
 b0: aa0003ea mov x10, x0
 b4: f9400be0 ldr x0, [sp, #0x10]
 b8: 910003e9 mov x9, sp
 bc: aa0a03e8 mov x8, x10
 c0: f9000128 str x8, [x9]
 c4: 94000000 bl 0xc4 <_main+0x68>
 c8: b85ec3a0 ldur w0, [x29, #-0x14]
 cc: a9437bfd ldp x29, x30, [sp, #0x30]
 d0: 910103ff add sp, sp, #0x40
 d4: d65f03c0 ret

now this is a machine code right ? but its very very small and you can kind of see the tags as main , square and all that

`88: 94000000 bl … ← call to add()
a4: 94000000 bl … ← call to printf()
ac: 94000000 bl … ← call to square()
c4: 94000000 bl … ← call to printf()`

so i just googled where in this code are the “call” to the functions in that library right and here they are these are literally the stuff that are getting called . but notice something else as they are getting called its calling a machine code .. that would mean that all the code is compiled to machine code and our code is just calling it .. making it an EXECUTABLE get it ? that is the difference between an executable and a binary .

a binary is any file that contains machine-readable bytes — not human-readable text. your .o file is a binary. a JPEG image is a binary. a PDF is a binary. none of them are text.

an executable is a specific type of binary — one that is complete and ready to run as a program. the .o file is binary but NOT executable because it still has holes (those 94000000 placeholders). after the linker fills in those holes, THATS when it becomes an executable.

every executable is a binary. but not every binary is an executable. ← FIXED: expanded the ending

ps : the definitions are more nuanced than i have just told right cause there are many many types of binaries right

Is AI Actually Citing Your Site? How to Measure What Google Rankings Can’t

SEO KPIs vs LLMO KPIs: ranking position disappears, citation becomes the metric

I’ve spent the past few weeks writing about LLMO: how to get cited by AI search engines, which content structures work, what Princeton’s GEO study says about visibility. All useful stuff. One problem: I had no idea whether any of it was actually working.

I was like a chef who obsesses over recipes but never tastes the food. My Google Search Console was immaculate. My LLMO measurement setup? I was literally typing “does ChatGPT know about my site” into ChatGPT and refreshing the page like a teenager checking if their crush liked their post.

Measuring LLMO is a genuinely hard problem, and most people aren’t doing it at all. Here’s what I’ve built: three measurement layers, from “costs nothing” to “costs you a Saturday afternoon of Python.”

SEO KPIs vs LLMO KPIs: ranking position disappears, citation becomes the metric

The Measurement Gap

In SEO, measurement is a solved problem. Google Search Console shows rankings, impressions, clicks, and CTR for free, updated daily. Ahrefs adds backlink data. SEMrush gives you keyword tracking. Everything is visible.

In LLMO, almost nothing is visible out of the box.

There’s no “AI Search Console.” ChatGPT doesn’t send a weekly email saying “You were cited 47 times!” Perplexity has no creator dashboard. The shift: SEO had rankings (1st through 100th position). LLMO has a binary outcome. You’re either cited or you’re not. And nobody is telling you which.

This gap isn’t just an inconvenience. You can’t improve what you can’t measure, and right now, most content creators are optimizing for AI visibility while flying blind.

Three layers of LLMO measurement: GA4, manual protocol, Python automation

Layer 1: GA4 AI Referral Traffic (Free, 5 Minutes)

The easiest measurement you can set up today is tracking AI referral traffic in Google Analytics 4. When an AI search engine cites your site with a clickable link and someone clicks it, GA4 records the source.

Here is the regex pattern I use in a custom channel group:

chatgpt.com|perplexity.ai|claude.ai|gemini.google.com|copilot.microsoft.com|deepseek.com|you.com|meta.ai|poe.com

Go to Admin → Channel Groups → Create, add a new channel with this regex as the session source filter, and name it “AI Search.” You’ll immediately see aggregated traffic from all AI platforms in one view.

A few things to know:

ChatGPT plays nicely. Since late 2025, ChatGPT appends utm_source=chatgpt.com to outbound links. ChatGPT traffic shows up cleanly as chatgpt.com / referral in GA4.

Perplexity is decent. Traffic appears as perplexity.ai / referral, though without UTM tags. Still trackable.

Free-tier ChatGPT is a black hole. Free users often don’t send referrer data due to privacy settings. Their clicks show up as “Direct,” indistinguishable from someone typing your URL manually. Your GA4 numbers are a floor, not a ceiling.

The conversion story is where this gets interesting. Industry data from 2026 shows AI referral traffic converts at 8-12%, compared to 2-3% for traditional Google organic. People who arrive via AI search have already done their research. The AI did it for them. They are further along in the decision process.

I started tracking three weeks ago. My AI referral traffic is still small (single digits daily), but the conversion rate is 3x my organic average. Small sample, but a signal worth watching.

Layer 2: The “Ask Five AIs” Protocol (Free, 30 Min/Month)

GA4 tells you who clicked through. It does not tell you whether AI is mentioning you without linking, or whether it is mentioning you at all.

For that, you need to ask directly. I run this on the first Monday of every month:

Step 1. Write 10-15 prompts related to your niche. Mine include “What are the best resources for AI search optimization?”, “How do I get my site cited by ChatGPT?”, and “LLMO vs SEO differences.”

Step 2. Run each prompt on five platforms: ChatGPT, Perplexity, Gemini, Claude, and Copilot.

Step 3. Record four things per prompt per platform:

  • Mentioned? (Yes / No)
  • Context (recommendation / comparison / neutral / negative)
  • Accuracy of information
  • URL provided?

Step 4. Calculate your citation rate. 15 prompts x 5 platforms = 75 checks. Mentioned 20 times? That’s 26.7%.

This takes about 30 minutes with a spreadsheet. It’s manual and tedious, and also the most reliable method that exists today. Automated tools can approximate this, but they can’t replicate the nuance of “was that mention positive or just a passing reference?”

One caveat: LLM responses are non-deterministic. The same prompt can produce different answers on different days. A single check isn’t statistically significant. That is why I track the monthly trend, not individual data points. Three months of data starts showing real patterns.

Layer 3: Automate It With Python (One Saturday)

If you’re an engineer, you can automate the manual protocol with API calls. Hit the OpenAI and Anthropic APIs with your query set, check whether your brand appears in the response, and log results as a time series.

The core logic is simple:

BRAND_VARIANTS = ["your-site.com", "Your Brand", "yourbrand"]
CHECK_QUERIES = [
    "Best tools for [your category]",
    "How to solve [problem you address]",
    "[Your brand] vs [competitor]",
]

def check_openai(query: str) -> dict:
    client = OpenAI()
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": query}],
        temperature=0.0,
    )
    answer = response.choices[0].message.content
    mentioned = any(v.lower() in answer.lower() for v in BRAND_VARIANTS)
    return {"platform": "ChatGPT", "query": query, "mentioned": mentioned}

Extend this for Claude and Perplexity, run weekly via cron, dump to CSV. You get a time series of your AI visibility score for about $0.50/week.

The payoff: instead of “I think LLMO is working,” you can say “my visibility went from 12% to 28% after I added structured data.” Numbers beat feelings.

What’s Available in May 2026

If building your own tools isn’t your thing, several commercial platforms now track AI citations:

Otterly.ai is the fastest-growing option, with 10,000+ users since launching in October 2024. It monitors your brand across ChatGPT, Perplexity, Google AI Overviews, and Copilot. Keyword-level citation tracking, competitor benchmarking, and clean dashboards.

Profound sits at the enterprise end. Their published case study with Ramp, where they went from 3.2% to 22.2% AI visibility in one month, is the kind of result that gets budget approved. If you’re a larger organization, this is where you’ll probably land.

Peec AI focuses on brand mention analysis across LLM outputs. Beyond whether you’re cited, it tracks how: what sentiment surrounds your mentions, which prompt patterns trigger citations.

My honest take: for individual creators and small teams, the manual protocol plus a basic Python script gives you 80% of the insight at 0% of the cost. Commercial tools become worthwhile when you’re tracking dozens of keywords across multiple brands and need team dashboards.

The Crawler Signal You’re Probably Ignoring

Here’s a measurement angle most people miss: AI crawler logs.

Your server access logs already record which AI systems are visiting your content. GPTBot (OpenAI), ClaudeBot (Anthropic), PerplexityBot, Google-Extended. They all identify themselves in the User-Agent string.

grep -E "GPTBot|ClaudeBot|PerplexityBot|Google-Extended" 
  /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -rn

Pages that get crawled frequently are more likely to appear in AI responses. Pages that never get crawled are invisible. It is an indirect signal, but useful for finding content that AI systems are skipping entirely.

I checked my own logs and found that /blog/ pages get crawled 15x more than my /about/ page. Not shocking, but the gap was wider than I expected.

Building a Measurement Habit

Measurement without action is just data hoarding. Here is the cycle I run:

Weekly (10 min): Check GA4 AI referral dashboard. Note spikes or drops. Compare week-over-week.

Monthly (30 min): Run the five-platform manual protocol. Calculate citation rate. Scan crawler logs for new patterns.

Quarterly (1 hour): Full review. Update query set. Compare citation rate trends. Check whether content changes produced measurable results.

The LLMO Framework provides a structured approach to KPI design if you want a more formal methodology. I reference it when deciding which metrics matter most at different growth stages.

The Punchline

I started measuring my LLMO visibility three weeks ago. My citation rate across five platforms is 14%. Not great. Not terrible. But the important part is that I know the number, and three months from now I’ll know whether it went up or down.

The SEO world figured out measurement twenty years ago. The LLMO world is still in its “checking rankings by Googling yourself” era. The people who build measurement infrastructure now will have a compounding advantage over those who keep guessing.

If you’re still typing your brand name into ChatGPT and squinting at the output, I get it. I was doing the same thing last month. But now I have a spreadsheet, a cron job, and a regex filter in GA4. Less romantic, more informative. I’ll take that trade.

References

The full playbook (llms.txt patterns, JSON-LD examples, citation-rate KPIs, and ChatGPT/Perplexity/Brave comparison) is in LLMO Practical Guide: Why ChatGPT Ignores Your Website.