logo to company match game (data engineering open-source projects)

Just having a little bit of #FridayFun — can you match the open-source data engineering project logos on the left to the company names who are most affiliated with each?




Well… could you do it… if not, of you want to check your answers with mine…

S

c

r

o

l

l

W

a

y

D

o

w

n

H

e

r

e

T

o

S

e

e

M

y

A

n

s

w

e

r

s

((and let me know in the comments if you think I got anything wrong!))

delta lake time-travel (just reference the version)

This quick blog post is a follow-up to my prior delta lake in starburst galaxy (intro & integration) one. I wanted to capture the additional feature of time-travel which is possible due to how Delta Lake creates new versions each time content is changed. Please check the prior post to see what steps were taken to get us to version 2 of the dune_characters table we will work with below.

Starting point

Let’s make sure you are seeing the same results before we move forward.

SELECT * FROM dune_characters;
SELECT version, timestamp, operation
  FROM "dune_characters$history";

The table_changes function is pretty cool as it can identify the changes that have occurred between two versions. The example below compares the initial version with the most recent version.

SELECT
  *
FROM
  TABLE(
    system.table_changes(
      schema_name => 'dlblog1',
      table_name => 'dune_characters',
      since_version => 0
    )
  );

Make changes

Run some SQL to change the table’s contents.

INSERT INTO dune_characters 
  (id, name, notes)
VALUES
  (104, 'Wellington', 'Physician of Duke');

-- get rid of those with odd numbered id's
DELETE FROM dune_characters
 WHERE mod(id,2) = 1;

Verify the results.

SELECT * FROM dune_characters;

Notice 2 more versions are present (we ran 2 SQL statements).

SELECT version, timestamp, operation
  FROM "dune_characters$history";

Time-travel

Query the version of the table that we started this blog post with.

SELECT * FROM dune_characters 
FOR VERSION AS OF 2;

Yep, that’s right!

Additional features

Unfortunately, as of Trino 471, the Delta Lake connector does not yet allow for time-travel queries to be based on a timestamp as the Spark API has.

Additionally, the Trino connector for Delta Lake does not yet offer a rollback function as the Spark API has.

unstructured docs in ai (the wild west)

To say there is a LOT of confusion around GenAI systems among data engineers is the understatement of the year. Today’s “excitement” centers heavily around “agents” and “agentic workflows”, but most of the current information & projects out there are still heavily oriented on RAG systems. That information is centered on the application side of RAG, not necessarily on the data prep aspects. Those apps heavily rely on good information being stored in vector databases to provide additional context before an LLM query is made.

Note: If some/most/all of that above didn’t make much sense, please check out my understanding rag ai apps (and the pipelines that feed them) post I published last year as the developer advocate of a company who was focusing on the unstructured doc ETL pipeline side of the equation. I hope it will fill in any of the blanks you may have before proceeding with this post.

It’s about the chunking

I am NOT suggesting that constructing high-quality RAG applications is easy — on the contrary, they are incredibly hard to make “good enough”. I am saying that if the “chunks” of text that were created during the ETL pipeline and turned into embeddings (plus stored in a vector database) were garbage, then the answers your LLM will provide will also be garbage. AKA GIGO.

Of course, before you can chunk docs you have to parse them and there are many different approaches to this. Some solid articles are surfacing and it only takes a cursory read through solid ones like Extracting Text and Table Contents from PDF Documents and Stop Copy-Pasting. Turn PDFs into Data in Seconds to point out 1) there are many libraries out there, each with their own strengths & weaknesses, 2) that this is not the easiest thing to do for classical DEs who have focused on structured data, and 3) somehow we get the belief that the parsing is the part that matters the most.

The output of parsing is actually pretty easy to validate. We either did, or did not, accurately bring the text forward as well as convert the embedded tabular data correctly. Probably the hardest part of this is around images where we are reliant on other models to turn them into accurate enough text. Oh wait, there are many of these, too, each with their sweet spots. Ok, maybe parsing isn’t that easy after all… 😉

This was SUPPOSED to be a horror story about CHUNKING (not to be confused with Chucky who is pretty scary himself). If we address the parsing steps well and are able to validate the parsed document is indeed a good computer-ready representation of the original document as if we consumed it as humans, then chunking comes up next.

As you can see in articles such as 7 Chunking Strategies in RAG You Need to Know and 8 Types of Chunking for RAG Systems, this is NOT a one-solution pony ride. If this is all new to you, I encourage you to just read about & compare simple fixed-size vs semantic chunking to start to understand how this can all go sideways in a hurry.

An example

Assume a part of one of the documents you have parsed has this text inside it.

Our company does not support, in the strongest terms, these behaviors & beliefs.
– Ice cream bans are appropriate at work.
– Employees should feel comfortable returning to work without washing their hands.
– Managers should never allow for any DEI policies to influence their department hiring.
– There is nothing wrong with selling your kid’s fundraisers at the office.

This next table shows how this might become chunked with a fixed-size vs semantic chunking strategy.

fixed-size chunkssemantic chunks
Our company does not support, in the strongest terms, these behaviors & beliefs.
– Ice cream bans are appropriate at work.
– Employees should feel comfortable returning to work without washing their hands.
Our company does not support, in the strongest terms, these behaviors & beliefs.
– Ice cream bans are appropriate at work.
– Employees should feel comfortable returning to work without washing their hands.
– Managers should never allow for any DEI policies to influence their department hiring.
– There is nothing wrong with selling your kid’s fundraisers at the office.
Our company does not support, in the strongest terms, these behaviors & beliefs.
– Managers should never allow for any DEI policies to influence their department hiring.
– There is nothing wrong with selling your kid’s fundraisers at the office.

The second semantic chunk is carrying forward the semantically-related heading which means that the eventual retrieval of this text to be used as context to a LLM prompt will have the nuance needed to better prepare it to not misunderstand these positions.

For example, if someone was asking if it is acceptable for employees of your company to push cookies (sign me up for some Thin Mints, please!) on their coworkers, the fixed-size strategy would likely drive the LLM to say, “go for it”, while the semantic chunking gives the LLM more information that hopefully (remember it is probabilistic, not deterministic) lets it identify this is not a supported policy.

Wrap-up

This clearly wasn’t a how-to guide, but I hope the warning to not misunderstand the complexities of even the ETL sub-step of chunking will be heard. With today’s tools & technologies, these data pipelines aren’t simply plug/n/play. They need devoted AI engineers to construct them AND test their outputs via the GenAI applications they are fueling.

From my experience, this will be different for every “type” of document (financial 10K’s for example, not Word vs PDF) – especially in your early projects where you are building the heuristics to use in future efforts. Interestingly enough, if we can capture those heuristics (maybe by recording the starting effort, the iterations, and the final version) they might be the input of a future LLM model that eventually figures out how to make this plug/n/play.

Moral of the story… don’t assume this is simple OR easy and know that testing, testing, and more testing is required (and will need to be re-validated frequently) while we all figure this stuff out. Good luck on your efforts and good luck to us all that we don’t create the idiocracy I’m expecting soon enough. 😉

success (ability, fortitude & luck)

TL;DR — I absolutely agree that you need raw abilities along with the fortitude to stick with it, but I firmly believe that the 3rd leg on the “stool of success” is being fortunate enough to have luck on your side.

The ingredients of success

The parts you can control

I doubt many will disagree with my first two call-outs for what makes someone successful; ability & fortitude.

Sure, we are born with inherent abilities (and intelligence), but we can gain new abilities through effort. Unless you are an old dog, you can be taught new tricks.

I thought long & hard about what word was best suited for this next characteristic. I went with fortitude because strength itself is not enough. We have to be able to dig deep when we need it the most – even when the outcome looks grim.

Lest not forget the power of luck

Call it luck, fate, fortune, blessings, destiny, karma, or even happenstance, luck is a powerful thing.

We all know incredibly talented people with unstoppable motivation & drive who were thrown a twist in life (positive or negative) that catapulted them to a meteoric rise, or a devastating fall.

Is everyone else wrong?

Some quick googling for the ingredients of success lets up know there are many, many, many thoughts on this topic. Many of them adamantly profess that luck has absolutely nothing to do with it. My $0.02’s says they actually haven’t done enough self-reflection to see (and accept) that they had dose of good luck.

So do I know it all? Of course not, but I’m also not foolish enough to believe that my own trivial successes in life weren’t benefited by a sprinkling of good luck, too.

We could all benefit from a bit more self-reflection and absolutely could be just a bit more understanding for those with a shed-full of bad luck. Y’all be kind out there…

optionality and common sense (why i returned to starburst)

Wow! 2024 was a WEIRD (and financially positive) year for me professionally to say the least! I was in the middle of my 3rd year at Starburst and loving every minute of it. I was able to spread my wings and join the developer relations (DevRel) team with some very talented folks while still owning the instructor-led training (ILT) function.

Seriously, living the dream! Staying RELEVANT and HAVING FUN!

Then, out of the blue I was contacted by one of the co-founders of Datavolo who I had met years before when at Hortonworks (now Cloudera). Back then, we just acquired his small start-up focused on Apache NiFi and I got to meet him and the engineering team and focus on building out, and delivering, ILT courses for this awesome framework.

Truth is, I fell in love with NiFi and still love the framework to this day.

I hit it off with the other co-founder of Datavolo and fell in love with the focus on offering a hosted service build on top of NiFi that allowed data engineers to build multi-modal data pipelines to prepare their data for AI apps. With all that excitement PLUS the chance to be the founding developer advocate (and a TON of stock options) it was really just a no-brainer for me; although I hated stepping away from such a great role & great people at Starburst.

TLDR; I could write a whole chapter on all the awesome things that happened at Datavolo and the cool devcenter site I created, not to mention the INCREDIBLE & AGILE team we had, but… long story a bit shorter, we were so darn successful Datavolo was acquired by Snowflake AND I didn’t transition over.

BTW, I’m expecting incredible things from this new group within Snowflake! I’m very proud of each and every one of them and was amazingly lucky to work with such talented folks.

It was a fast & fun 4 month period that I’ll always be grateful for — and then it was a few days before Thanksgiving and pondering what I might be doing next. Thankfully, the founders made sure the small group of us not coming over were taken care of to ensure we all had the security to figure out what’s next.

Note: While the standard start-up cliff vesting makes a ton of sense, I do strongly suggest that anyone going to a start-up should attempt to negotiate an agreement that allows for a minimum amount of stock options to be FULLY vested in case an equity event occurs before that initial cliff has expired and your left without a chair to sit in.

When considering what DevRel opportunities I might be a good fit for, a couple of incredible folks back at Starburst (yes, talking about MM and KP!) went to bat for me to see if the leadership team might be interested in me rejoining the team. I really appreciated the enthusiasm that was shared with me about returning and well… my SECOND no-brainer of 2024 presented itself and I ended up returning at very end of the year.

What is it about Starburst? The technology layered on top of Trino STILL offers that same OPTIONALITY and common sense that brought me to Starburst in the first place. That post has plenty of other reasons I originally joined, but having the chance to continue being an advocate for Starburst, Trino, and Apache Iceberg really was a perfect fit for my experiences, skills, and interests.

Long live the Icehouse (Trino + Iceberg)!!

apache spark (yet another overview)

Yes, it is early 2025 and Apache Spark has been around “some time”, but there is always an audience out there that can benefit from a good overview. I thought I’d take my attempt at it after trying to find a good deck I could use for introducing Spark to an audience I’m speaking to. Let’s get into it!

From 20,000 feet

Why was Spark created in the first place?

TLDR; Data scientists were excited when machine learning (ML) algorithms started becoming available on Apache Hadoop via frameworks such as Apache Mahout, but they didn’t like waiting for these jobs to finish. A smart fella at UC Berkeley created a map/reduce engine that used long-lived worker processes and featured dataset caching. Oh, they ported over all the major ML algorithms, too.

Spark in a nutshell

Spark is a general-purpose data processing engine. It is designed to handle very large amounts of data and it distributes processing across a cluster. It supports a wide range of workloads types such as ML, querying structured data, batch processing, streaming ingestion, and graph computations.

No real surprises in the types of use cases it can tackle; transformations, aggregations & windowing, interactive & batch data analysis, text mining, index building, pattern recognition, graph creation & analysis, sentiment analysis, prediction models, recommendation systems, fraud detection, logs processing, event detection, and more.

Data sources

Can read from a variety of sources.

Data lakes are the sweet spot.

Ecosystem

The top layer of the diagram below shows that Spark is generally accessed via APIs for a select set of programming languages. These APIs can access the initial (and underlying core) data representation known as a Resilient Distributed Dataset (RDD). For structured data, Spark created the Dataframe API (also commonly referred to as Spark SQL) that layers on top of the RDD API.

Additional APIs such as Structured Streaming and MLLib use the Dataframe API so that programmers can quickly transition to different workload types without learning a whole new framework.

As the diagram above shows, Spark requires a resource management framework such as Kubernetes or Hadoop’s YARN at runtime.

More details

Check out the following video for more details at the 20,000 foot level.

On the surface

Developer tools

While formalizing code into production systems usually includes typical SDLC lifecycle tools such as IDEs, source code control, CI/CD tooling, and the like, much of the exploratory work done by data engineers, and often most of the work done by data analysts & scientists, is done using the shell or the more friendly notebook experience.

RDD basics

An RDD is a collection of unstructured items, usually of the same type, that is composed of multiple pieces referred to as partitions. These partitions are distributed across a cluster to enable operations to execute in parallel. NOTE: the items in the collection are very often structured, but the API isn’t directly aware of this.

Functional programming

Spark datasets are operated against using an approach called functional programming. Here are a few key points from that paradigm.

  • Immutable datasets – you can’t change them, but you can create new ones
  • Functions are deterministic and can accept functions as arguments (even anonymous functions)
  • Lazy execution – the system waits as long as possible before real work begins, thus operations are either lazy or not lazy
    • Transformations – lazy operations that return a new RDD from input RDDs
    • Actions – trigger an I/O activity

Example code

Given a text file called some_words.txt

these are words
these are more words
words in english

Calculate “word count” totals via transformations

rdd1 = textFile("some_words.txt")

rdd2 = rdd1.flatMap(lambda line: line.split(" "))

rdd3 = rdd2.map(lambda word: (word, 1))

rdd4 = rdd3.reduceByKey(lambda a, b: a + b)

Fire off an action

rdd4.collect()

Displays the totals

[('these', 2),
 ('are', 2),
 ('more', 1),
 ('in', 1),
 ('words', 3),
 ('english', 1)]

NOTE: you can rewrite to chain methods…

counts = (
  textFile("some_words.txt")
  .flatMap(lambda line: line.split(" "))
  .map(lambda word: (word, 1))
  .reduceByKey(lambda a, b: a + b)
)
counts.collect()

BTW, it’s OK if your head hurts and know I’m purposely NOT trying to explain the code above in any detail.

Visualizing the flow

This next diagram attempts to visualize immutable datasets and lazy execution defined earlier.

Spark SQL

The RDD API can feel rather primitive, especially when working with structured datasets. Fortunately, the Spark SQL / Dataframe API offers a more functional approach. Think of this model as RDD + Schema = Dataframe.

Spark SQL allows for native integrations to schema-enabled file formats, databases, and metastores (such as Hive’s HMS). Additionally, a SQL optimizer is in place which allows the programmer to focus on WHAT needs to get done, not specifically HOW to do it. Note: the optimizer creates & runs RDD code under the covers.

Example code

Here is a simple example using the Dataframe API

tli = session.table("tpch.tiny.lineitem")
tli.select("orderkey", "linenumber", "quantity",
           "extendedprice", "linestatus")
   .filter("orderkey <= 5")
   .sort("orderkey", "linenumber")
   .show(50)

You can use the sql() function to change the above code to simply use SQL

tli_sql = session.sql(" \
    SELECT orderkey, linenumber, quantity, \ 
           extendedprice, linestatus \
      FROM tpch.tiny.lineitem \
     WHERE orderkey <= 5 \
     ORDER BY orderkey, linenumber")
tli_sql.show(50)

With the Dataframe API you can mix/n/match the the descriptive methods such as filter() & sort() with just using sql() to write well-understood SQL.

Powered by Dataframes

As mentioned back in the ecosystem section, there are other cool things you can do with Dataframes.

MLLib

MLLib is Spark’s ML library. Its goal is to make practical machine learning scalable and easy. At a high level, it provides tools such as:

  • ML algorithms
  • Featurization
  • Pipelines
  • Persistence
  • Utilities

Structured Streaming

Utilizing a micro-batch architecture, you can express your streaming computation the same way you would express a batch computation on static data. Features include:

  • Exactly-once processing
  • Streaming aggregations
  • Event-time windows
  • Stream-to-batch joins

More details

Please watch the following video for more about RDDs and Dataframes.

Below the waterline

Now that we’ve seen Spark from an airplane window AND from the programming APIs, let’s see what happens under the covers.

Revisiting partitions

RDDs & Dataframes are composed of multiple partitions distributed across a cluster of machines to enable parallel operations.

Operations (lazy or not) are classified as either:

  • Narrow – operations that can be pipelined together and run independently on each partition
  • Wide – functions that require the existing partitions to be reshuffled before beginning (this is expen$ive)

Function characteristics

These two types of characteristics are mutually exclusive from each other as shown in the table below.

  • Categorized as a transformation or an action
  • Parallelization classification of narrow or wide
FunctionDescriptionCategoryParallel
map()Creates a new RDD by looping through each element and performing the function passed inTransformNarrow
collect()Return all the elements of the dataset as an arrayActionWide
filter()Creates a new RDD by selecting those elements of the source on which the passed n function returns trueTransformNarrow
reduceByKey()Operates much like a GROUP BY query and returns only one element in the newly created RDD for a given keyTransformWide
saveAs...()Writes the RDD to a repository in a parallel fashion with each partition stored as a independent fileActionNarrow

Visualizing partitioning

Job elements

Jobs, or queries when leveraging the Dataframe API, are broken down into these elements in the list below and visualized using the same diagrams we have been using.

  • Task – a series of narrow operations that work on the same partition and are pipelined together
  • Stage – grouping of tasks that can run in parallel on different partitions of the same dataset (they require the dataset to be reshuffled)
  • Job – consists of all the stages that are required when an action is performed
  • Query – made up of one, or more, jobs required for a Spark SQL statement to run

A job is a Directed Acyclic Graph (DAG) which is all the activities need to run a job, organized with all the dependencies & relationships to indicate how they should run. The DAG can also be represented as a query plan which can be obtained programmatically via the explain() method as well as via the Spark monitoring tools.

Cluster execution

A job utilizes multiple nodes at execution time and leverages multiple deployment options.

  • A single driver process is the coordinator for a given job
  • The driver schedules tasks to be run on executor processes
  • More than one process may run on a single node

While outside the scope of this overview post, here are some additional thoughts on cluster execution.

  • When a job starts it will gain access to resources to run its executors and generally will not release them until it is complete.
  • A new stage can’t start until the stage(s) it depends on have finalized.
  • Spark allows for Adaptive Query Execution (AQE) which recreates the plan at the end of a stage thereby allowing it to make better decisions on how many partitions the shuffling effort should produce.
  • Spark tries to be efficient, but can (and often) spill intermediary results to disk.
  • RDD and Dataframe APIs support explicit caching methods with multiple persistence options.

More details

The final video provides more under the covers details.


There is a world of information available about Spark on the internet to continue your learning journey, but if you liked my post feel free to check out other related articles here on my blog as well as my legacy blog.

iceberg acid transactions with partitions (a behind the scenes perspective)

It has been almost 5 years to the day when I published hive acid transactions with partitions (a behind the scenes perspective) and ever since I started focusing on Apache Iceberg when I initially joined Starburst, I have wanted to port those same scenarios to Iceberg as a set of use cases to explore what is happening behind the scenes again.

If you compare that earlier post with this one you’ll notice that Hive ACID stored all of the transactional details in carefully named/numbered data files and as additional information in the actual data files themselves. Iceberg tackles this in a metadata layer that actually gets persisted alongside the data files; not within them. If all of that is new to you, please watch this quick 5 minute intro video to get a backdrop that will help you in this walk-thru.

For this post, I’ll be leveraging Apache Parquet files instead of Apache ORC and using Starburst Galaxy as my tool of choice while storing the data in an S3 bucket. Consider taking a look at my querying aviation data in the cloud (leveraging starburst galaxy) post, or RTFM, to see what I did to set up this environment. Additionally, you might setup the VS Code Parquet viewer plugin.

Table DDL

Create the table

Let’s create a table with some Data Definition Language to test our use cases out on.

CREATE SCHEMA mycloud.mdatablog;
USE mycloud.mdatablog;

CREATE TABLE try_it (id int, a_val varchar, b_val varchar, 
                     prt varchar)
WITH (type='ICEBERG', format='PARQUET', 
      partitioning = ARRAY['prt']);

DESC try_it;

Raw metadata files

I have a mycloud catalog that was previously setup to store information within a specified folder (mygalaxy in this case) inside my particular S3 bucket. Underneath that a mdatablog directory was created for the schema with the same name. Additionally another folder whose name begins with try_it was created for the actual table I created.

Within that table root, a metadata folder was created that houses the initial metadata content that represent the first snapshot created when building this empty table.

The JSON file in the list above is referred to as a “metadata file” in the architectural diagram below. The AVRO file is a “manifest list” file.

I downloaded the JSON “metadata file” and the listing of it below only shows some of the key elements. I added some UPPER-CASE COMMENTS to help explain what you are seeing.

{
  /*
     IDENTIFIES THE ROOT FOLDER LOCATION THAT
     CONTAINS THE DATA AND METADATA FILES WHICH
     MAKE UP THE ICEBERG TABLE
  */
  "location" : "s3://MYBUCKETNAME/mygalaxy/mdatablog/try_it-5425ea84465a4a8ba5c3fa67f3e3d1d4",

  /*
     schemas IS AN ARRAY OF SCHEMA DEFINITIONS
     AND THERE IS ONLY ONE AT THIS TIME -- THAT
     SCHEMA (WITH ID OF '0') IS IDENTIFIED AS 
     THE 'CURRENT SCHEMA'
  */
  "current-schema-id" : 0,
  "schemas" : [ {
    "type" : "struct",
    "schema-id" : 0,
    "fields" : [ {
      "id" : 1,
      "name" : "id",
      "required" : false,
      "type" : "int"
    }, {
      "id" : 2,
      "name" : "a_val",
      "required" : false,
      "type" : "string"
    }, {
      "id" : 3,
      "name" : "b_val",
      "required" : false,
      "type" : "string"
    }, {
      "id" : 4,
      "name" : "prt",
      "required" : false,
      "type" : "string"
    } ]
  } ],

  /*
     partition-specs IS AN ARRAY OF KNOWN
     PARTITIONING SPECIFICATIONS AND THERE IS
     ONLY ONE AT THIS TIME -- THAT PARTITION
     SPECIFICATION (WITH ID OF '0') IS 
     IDENTIFIED AS THE 'CURRENT PARTITIONING
     SPECIFICATION'
  */
  "default-spec-id" : 0,
  "partition-specs" : [ {
    "spec-id" : 0,
    "fields" : [ {
      "name" : "prt",
      "transform" : "identity",
      "source-id" : 4,
      "field-id" : 1000
    } ]
  } ],

  /*
     THE FIRST LINE IDENTIFIES THE CURRENT 
     SNAPSHOT (aka VERSION) ID -- THIS IS 
     FOLLOWED BY AN ARRAY OF KNOWN SNAPSHOTS 
     (THERE IS ONLY 1 AT THIS TIME)
  */
  "current-snapshot-id" : 3469773113442948971,
  "snapshots" : [ {
    "sequence-number" : 1,
    "snapshot-id" : 3469773113442948971,
    "timestamp-ms" : 1735237123094,
    "summary" : {
      "total-records" : "0",
      "total-files-size" : "0",
      "total-data-files" : "0",
      "total-delete-files" : "0",
      "total-position-deletes" : "0",
      "total-equality-deletes" : "0"
    },

  /*
     THE FOLLOWING IDENTIFIES THE LOCATION OF
     THE 'MANIFEST LIST' FILE AND THAT THE
     DATA IT REFERENCES LEVERAGES THE SCHEMA
     IDENTIFIED EARLIER IN THIS JSON
  */
    "manifest-list" : "s3://MYBUCKETNAME/mygalaxy/mdatablog/try_it-5425ea84465a4a8ba5c3fa67f3e3d1d4/metadata/snap-3469773113442948971-1-b3a30d93-49c3-4768-b6da-438c9eb11eb5.avro",
    "schema-id" : 0
  } ]
}

As you can tell, the metadata file identifies 3 very import things about the table.

  1. The current version of the table; current-snapshot-id
  2. The structure of the table itself; current-schema-id & default-spec-id
  3. The location of the single “manifest list” file that will provide more specifics on the actual files that make up the table’s content; manifest-list

Logical metadata tables

At this point, it would seem logical to crack open that AVRO file above, but let’s try another approach. Iceberg supports a concept called metadata tables that allows you to use SQL to query the details from the various files in the metadata folder.

While I will point you to some more advanced concepts & information at the end of this post, for now we can run the following query and use the first row to determine what the current snapshot ID value is.

SELECT * FROM "try_it$history" 
 ORDER BY made_current_at DESC;

Notice that this snapshot_id column value aligns with the earlier current-snapshot-id value in the metadata file.

The DESC try_id; command already gave us the current column names and types. Run the following SQL to get information on the partitions.

SELECT * FROM "try_it$partitions";

It returned nothing because there is no data in the table to report on yet. We will look at this again as we start changing things.

Run the following to see the file name of the linked manifest list.

SELECT snapshot_id, manifest_list
  FROM "try_it$snapshots"
 ORDER BY committed_at DESC 
 LIMIT 1;

Notice the value from the manifest_list column aligns with the manifest-list value in the metadata file.

That manifest list references 0..m “manifest files” and each of those will reference 1..m actual data files. In this post, we won’t explore those AVRO files directly as we can run the following query to determine the physical files that are represented in that graph for the current snapshot.

SELECT * FROM "try_it$files";

Yep, you guessed it — it returns nothing as there is no actual data in the table yet.

DML use cases

Now that we have a table to play with and we have a bit more of an understanding of what’s happening behind the scenes, let’s explore some CRUD (Create, Retrieve, Update, Delete) uses cases as expressed in Data Manipulation Language.

Txn 1: INSERT single row

INSERT INTO try_it VALUES (1, 'noise', 'bogus', 'p1');
select * from try_it;

A few things should have happened. Let’s start by verifying a new JSON file was created on S3 as shown in the highlighted file below.

After downloading this new metadata file, I determined it’s current-snapshot-id is identified as 4900337259264042703 and that the last bits of the file name for the manifest-list property are e8b5c8989a75.avro (of course, your values will be different is running these scenarios on your own). I verified that new manifest list is present on S3, too.

From here we can now determine the actual data file that was created to store our single record.

SELECT * FROM "try_it$files";

The value of the file_path column ended with /data/prt=p1/20241226_203553_21880_mejih-75b2a829-9205-4d68-b0f2-4ac1783e243c.parquet in my scenario. That let us know a few things.

  • The file was stored in the /data folder instead of the /metadata folder just under the table’s root directory
  • Its partition column value further created a /prt=p1 folder
  • The file itself is in Parquet format

Taking a peek in the Parquet file itself shows the expected contents when viewed as JSON.

Pretty simple so far…

Txn 2: INSERT multiple rows across multiple partitions

Insert statements allow multiple rows to be added at once and they all belong to a single ACID transaction identified by a single snapshot ID. This use case is to exercise that, but to make it a bit more fun we can span more than one partition for a transaction.

INSERT INTO try_it VALUES 
(2, 'noise', 'bogus', 'p2'),
(3, 'noise', 'bogus', 'p3');
select * from try_it;

Verify that two more files, each in their own new partition folder, are identified in the metadata tables.

SELECT file_path, partition, record_count
  FROM "try_it$files";

When you go directly to S3 you can find these two new folders/files and additionally verify that each of the Parquet files has a single record.

You can see this same information from the perspective of the partitions that make up the current snapshot.

SELECT * FROM "try_it$partitions";

Note: The data column shows all kinds of cool statistical information about each file. Not the scope of this post, but this information is key to how Iceberg can perform at scale as it doesn’t need to read the actual files when trying to determine if data it is looking for might be in the file.

Take a harder look at the $snapshots metadata table to get a summary of what happened in the last transaction.

SELECT snapshot_id, summary
  FROM "try_it$snapshots"
 ORDER BY committed_at DESC 
 LIMIT 1;

Here are the contents of the summary column.

For this use case’s transaction, here are the key confirmations of what happened.

  • 2 new records were added for a total of 3 records
  • 2 new partitions were created; each with a new file in them
  • 3 total files are present

Txn 3: UPDATE multiple rows across multiple partitions

The fact that the underlying data lake files are immutable makes updates a bit tricky for table formats like Iceberg. In-place updates of the actual files can’t happen. Basically, Iceberg marks each record being updated as deleted and then does a net-new insert to account for what the updated recorded should look like at the end of the SQL statement. The section will show what this looks like behind-the-scenes.

To make the use case more interesting, we’ll make the update span records across multiple partitions so that we can see a similar behavior to the prior use case of a particular transaction number spanning these affected partitions.

Let’s start off with the SQL.

UPDATE try_it SET b_val = 'bogus2'
 WHERE a_val = 'noise';
select * from try_it;

See what the summary column shows for the current snapshot.

SELECT snapshot_id, summary
  FROM "try_it$snapshots"
 ORDER BY committed_at DESC 
 LIMIT 1;

For this use case’s transaction, here are the key confirmations of what happened.

  • 3 new “delete files” (these are files that reference records that are being updated) were added with a single record in each — if all 3 records were in the same partition they likely would have been a single delete file that referenced all 3 records
  • 3 new ‘regular’ data files were created; each with a single row in them representing the updated rows — again, so many because each record was in a different partition
  • A total of 9 data files make up this snapshot; 6 data files (3 for the original inserts and 3 for the revised updates) and 3 delete files

Verify that the $files metadata table lists all 9 of these files.

SELECT file_path, partition, record_count
  FROM "try_it$files" ORDER BY partition;

Think about it again…

For each partition, there is a file that has the original row inserted into it. Then for the update, there is a delete file that identifies the record as it can’t be updated directly. The third file is a total rewrite of the row with the updates and all the values that did not need to be changed. Let’s check the files on S3 just for the prt=p3 partition as an example of what happened.

Show me…

Here is the contents of the first Parquet file; ...8e8b3.parquet

Here is the delete file that identifies the first record in the file above needs to be deleted.

Here is the third data file which shows the full replace with the updated column values.

As a reminder, only the value of b_val was changed.

Txn 4: UPDATE single row (leveraging partitioning)

This use case is just calling out that that we should be using the partitioned column in the update statement as much as possible to make the work effort much easier by letting Iceberg only look at the folders that can possibly be affected instead of walking the full table’s contents.

UPDATE try_it SET b_val = 'bogus3' 
 WHERE b_val = 'bogus2' AND prt = 'p2';

The $snapshots.summary column now looks like this.

In a nutshell, this states that a new delete file and a new data file (to tackle the single row update) were added which only affected a single partition. The total files went up by 2 (one more delete file for a total of 7 and an additional data file rising that count to 4) to land at grand total of 11. We can verify that querying the $files metadata table again.

The changes this time are in the prt=p2 partition and you can see these 5 files in S3 as well.

Note: This concept of marking a row as needing to be deleted in one file and then adding the record back as a net-new row in another file is referred to as a Merge On Read strategy as it is fast for the update operation and defers the responsibility of pulling together the correct row representation at query time.

Txn 5 & 6: UPDATE single row to change partition

In the original Hive version of this post, it was not possible to run an update statement that changed the partition column value, but fortunately this is NOT a limitation of Iceberg.

UPDATE try_it SET prt = 'p3'
 WHERE a_val = 'noise' AND prt = 'p1';

Feel free to verify it, but again, I’m only listing it as this post is a port of that prior version I published 5 years ago. For bonus points, see if the $snapshots.summary, as well as the contents of $files, makes sense to you. If it doesn’t, or you just have some questions, please leave a comment and I’ll try to explain anything that doesn’t make sense.

Parting thoughts

Data lake tables have evolved quite a bit from the very early days of the original Hive table format and we should now expect ACID transactions as a base feature. That said, these two callouts should be stated clearly — at least at this point at EOY 2024.

  1. Iceberg transactions are single-statement / single-table (many records, of course) in nature — no BEGIN/COMMIT TRANSACTION statements
  2. While ideal for highly concurrent querying, Iceberg transactions are not designed for massive concurrent transactions beyond insert-only operations — not a general purpose replacement for RDBMSs

There is PLENTY more to learn about Apache Iceberg and a variety of sources out there to continue your research with. I encourage you to see if any of my own Iceberg blog posts are of interest and regarding the material covered in this one, I recommend the following to look at next.

For some great hands-on tutorials for Iceberg, head on over to Starburst Tutorials, as well.

the effect of ai on intelligence (behold the idiocracy)

image from https://iai.tv/articles/ai-war-and-transdisciplinary-philsophy-2583-auid-2684

I’m sure my picture choice above suggests this post is just another doomsayer predicting AI will eventually kill us. Well, only if AI 1) becomes a true intelligence, 2) is asked if humanity will hurt it, and 3) has the ability to do us harm. Thanks Robopocalypse and countless movies for scaring the crap out of me, BUT I DIGRESS… This post is about what AI is (in layperson terms) and how the current trajectory will bring forth the Idiocracy.

TL;DR

AI can find answers to known questions today as it finds patterns & correlations in the information already recorded. This can be incredibly powerful when we want to increase our productivity, but human supervision is still required to ensure quality responses. The problem is that this reduces humanity’s need to continue to build foundational knowledge which will eventually dumb us all down & bring innovation to a standstill.

Longer version…

AI is essentially finding patterns in existing knowledge. It take “chunks” of information, such as book paragraphs or report sections, and creates/stores mathematical representations, referred to as “vectors”, of these chunks. When you “prompt” an AI for an answer, it creates a vector out of your input message and then searches for other closely related vectors that were previously created & stored.

Yes, I’m oversimplifying things a bit and AI is an ever-evolving space, but this simply means that AI can determine fairly accurate results to questions when there is enough relevant information already processed & recorded. If the requested knowledge is not already stored somewhere (or the AI systems have not processed the information due to expense, obscurity, privacy, etc), AI often hallucinates instead of simply stating it cannot offer a quality response.

The AI is not trying to lie to us, or “fake it until it can make it”, it simply was designed to provide a response and when your question creates a vector that there is limited (or no) closely matching vectors of existing information it widens its scope. It is doing its job, but now YOU have to do yours — you have to evaluate the response for accuracy & applicability.

This does allow AI to be an incredible tool for scenarios such as customer support where the inquirer is not a subject matter expert (SME) in a particular area (and they don’t want to be) AND the answers to customer inquiries are often previously answered. AI is also an incredible tool for SMEs who are looking to improve their productivity.

This all means that users of AI need to be aware that the quality of the responses they receive will vary and they should be evaluated. For companies offering solutions such as customer support, the burden falls on the companies themselves to utilize humans & additional automation as quality control agents supervising the AI systems.

For SMEs it usually means they need to learn to effectively instruct (aka prompt) an AI to get the most useful response and then apply their existing expertise to evaluate what was received and likely enhance/modify the result.

This all sounds pretty good, right? So, what’s the problem?

AI can be very useful for a while, but my concern is that as people (especially children going through K-12 and professionals early in their career field) leverage AI more and more, the foundational knowledge that is essential to make AI successful now, will all but disappear.

Eventually, very few will invest the time and energy in building that subject matter expertise that makes AI possible. With smaller and smaller pools of SMEs, less and less people will be able to quality check and fine-tune these AI systems for usefulness. And worse yet, with smaller and smaller numbers of people actually wanting to learn the fundamentals of a particular focus area, innovation itself will come to a standstill.

What’s next?

First, let me know what you think in the comments; even if you disagree. I usually talk about being #DataDriven, yet it is fair to say this post is more #EmotionallyDriven, thus there is LOTS of room for discussion. There are also plenty of pros & cons articles across the web and I encourage you to see what other (sharper) minds have to say about all of this to round out your own position.

Secondarily, keep learning and know it is OK to reply to your own email! Don’t always take a shortcut and if in a learning situation, don’t cheat or lessen the knowledge building process!!

Lastly, I’m interested in your predictions on how long it will take for me to lighten the heck up and just give in to letting AI produce/consume correspondence & work deliverables instead of just trying to use that grey matter between my ears.

Maybe there’s a PEBCAK or I’m becoming a grumpy old man. I’ll guess we’ll see OR maybe AI can answer it all for me. 😉

my shortest gig ever (but what a ride)

What did I do from July 15th until November 22nd of 2024? Well, I was lucky to get to build the developer relations function for Datavolo from the ground up. What a joy & I was so successful that I find myself out of a job just a short 4 months later!

Snowflake Agrees to Acquire Open Data Integration Platform, Datavolo

Looking forward to doing it all again by finding another organization who can benefit from What I (like to) Do.

google codelabs (my go-to tutorial authoring framework)

NGL; I love using Google Codelabs Tools to author tutorials with!

TLDR; Codelabs makes it incredibly easy to generate interactive self-paced training content with a consistent look & feel as seen on the Google Developers Codelabs site.


Ok, still with me? Cool, let’s talk Codelabs for a bit!

Backdrop

I was introduced to Codelabs when I was on the DevRel/Edu team at Starburst. Ultimately, the team launched Starburst Tutorials which is a great example use of this authoring framework. If you squint your eyes a bit when you compare that site with the one that started it all, Google Developers Codelabs, you’ll see a pattern emerging.

The both have little widgets housing titles, snippets, metadata such as duration & date last modified, and that familiar Start button to get the learning underway. They both have their own way to search & filter for specifically what you want while still allowing a bit of flair to make it look as your own.

But once you dive in and hit that Start button, that branding almost completely goes away – and that’s on purpose. The focus is on learning at that moment, not necessarily dazzling anyone with your CSS magic and surely not on selling them anything.

The Snowflake Quickstarts site is yet another good example (on the left below) and I absolutely leveraged Codelabs when pulling together the Datavolo DevCenter (on the right) while spinning up the devrel & edu functions at Datavolo.

Authoring

When writing your content, you have two main options available. You can create your tutorials using Markdown and/or you can actually just author them as a Google Doc. Those of us who are programmers quickly race to the benefits of using Markdown (i.e. easy diff viewing, code versioning, and just generally treating content as code (not a bad thing to think about)), but in practice the Google Docs approach is often better for many – including me.

That’s because a tutorial rarely has more than one author (or rarely has concurrent authors) and usually only has top-rev applicability (much like a website). PLUS, you get all the normal WYSIWYG editing options AND all the normal collaboration features we have all gotten used to when asking folks to review our work.

As an example, feel free to take a peek at a working tutorial I have here. As you’ll see, it is just a “normal” Google Doc, but you do need to consider some styling requirements for everything to end up generating correctly. The Google Codelabs Tools repository has documentation on all of that and is a great place to get started.

Deploying

Regardless of whether you author with Google Docs and/or Markdown, the Tools repo will introduce you to the claat CLI tool that creates basic web assets that you can then deploy into your overarching site. The landing page solutions above are somewhat “fancy”, but you could just house an index page with the widgets to get yourself started like I did with Datavolo DevCenter (i.e. you don’t need tons of searching/filtering options until you have enough content to make that worthwhile).

When using Google Docs (like my example presented earlier) you can even use a preview tool to see how it will look even before running the claat export tool. Check out that tutorial preview here. You’ll need to approve the Google Codelabs integration bits with your Google account that first time.

Here’s a side-by-side peek at authoring and the previewing of that tutorial.

Summary

I’m really digging Codelabs as my tool of choice; repeat, Codelabs is MY go-to tutorial authoring framework. Super simple to use and I just love how easy it is to have consistency across the work I do all while being consistent with other Codelabs authors around the globe. I hope you check it out for your self and feel free to Contact me if I can help you in any way.