|
| 1 | +--- |
| 2 | +# Licensed to the Apache Software Foundation (ASF) under one |
| 3 | +# or more contributor license agreements. See the NOTICE file |
| 4 | +# distributed with this work for additional information |
| 5 | +# regarding copyright ownership. The ASF licenses this file |
| 6 | +# to you under the Apache License, Version 2.0 (the |
| 7 | +# "License"); you may not use this file except in compliance |
| 8 | +# with the License. You may obtain a copy of the License at |
| 9 | +# |
| 10 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | +# |
| 12 | +# Unless required by applicable law or agreed to in writing, |
| 13 | +# software distributed under the License is distributed on an |
| 14 | +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| 15 | +# KIND, either express or implied. See the License for the |
| 16 | +# specific language governing permissions and limitations |
| 17 | +# under the License. |
| 18 | + |
| 19 | +name: beam-concepts |
| 20 | +description: Explains core Apache Beam programming model concepts including PCollections, PTransforms, Pipelines, and Runners. Use when learning Beam fundamentals or explaining pipeline concepts. |
| 21 | +--- |
| 22 | + |
| 23 | +# Apache Beam Core Concepts |
| 24 | + |
| 25 | +## The Beam Model |
| 26 | +Evolved from Google's MapReduce, FlumeJava, and Millwheel projects. Originally called the "Dataflow Model." |
| 27 | + |
| 28 | +## Key Abstractions |
| 29 | + |
| 30 | +### Pipeline |
| 31 | +A Pipeline encapsulates the entire data processing task, including reading, transforming, and writing data. |
| 32 | + |
| 33 | +```java |
| 34 | +// Java |
| 35 | +Pipeline p = Pipeline.create(options); |
| 36 | +p.apply(...) |
| 37 | + .apply(...) |
| 38 | + .apply(...); |
| 39 | +p.run().waitUntilFinish(); |
| 40 | +``` |
| 41 | + |
| 42 | +```python |
| 43 | +# Python |
| 44 | +with beam.Pipeline(options=options) as p: |
| 45 | + (p | 'Read' >> beam.io.ReadFromText('input.txt') |
| 46 | + | 'Transform' >> beam.Map(process) |
| 47 | + | 'Write' >> beam.io.WriteToText('output')) |
| 48 | +``` |
| 49 | + |
| 50 | +### PCollection |
| 51 | +A distributed dataset that can be bounded (batch) or unbounded (streaming). |
| 52 | + |
| 53 | +#### Properties |
| 54 | +- **Immutable** - Once created, cannot be modified |
| 55 | +- **Distributed** - Elements processed in parallel |
| 56 | +- **May be bounded or unbounded** |
| 57 | +- **Timestamped** - Each element has an event timestamp |
| 58 | +- **Windowed** - Elements assigned to windows |
| 59 | + |
| 60 | +### PTransform |
| 61 | +A data processing operation that transforms PCollections. |
| 62 | + |
| 63 | +```java |
| 64 | +// Java |
| 65 | +PCollection<String> output = input.apply(MyTransform.create()); |
| 66 | +``` |
| 67 | + |
| 68 | +```python |
| 69 | +# Python |
| 70 | +output = input | 'Name' >> beam.ParDo(MyDoFn()) |
| 71 | +``` |
| 72 | + |
| 73 | +## Core Transforms |
| 74 | + |
| 75 | +### ParDo |
| 76 | +General-purpose parallel processing. |
| 77 | + |
| 78 | +```java |
| 79 | +// Java |
| 80 | +input.apply(ParDo.of(new DoFn<String, Integer>() { |
| 81 | + @ProcessElement |
| 82 | + public void processElement(@Element String element, OutputReceiver<Integer> out) { |
| 83 | + out.output(element.length()); |
| 84 | + } |
| 85 | +})); |
| 86 | +``` |
| 87 | + |
| 88 | +```python |
| 89 | +# Python |
| 90 | +class LengthFn(beam.DoFn): |
| 91 | + def process(self, element): |
| 92 | + yield len(element) |
| 93 | + |
| 94 | +input | beam.ParDo(LengthFn()) |
| 95 | +# Or simpler: |
| 96 | +input | beam.Map(len) |
| 97 | +``` |
| 98 | + |
| 99 | +### GroupByKey |
| 100 | +Groups elements by key. |
| 101 | + |
| 102 | +```java |
| 103 | +PCollection<KV<String, Integer>> input = ...; |
| 104 | +PCollection<KV<String, Iterable<Integer>>> grouped = input.apply(GroupByKey.create()); |
| 105 | +``` |
| 106 | + |
| 107 | +### CoGroupByKey |
| 108 | +Joins multiple PCollections by key. |
| 109 | + |
| 110 | +### Combine |
| 111 | +Combines elements (sum, mean, etc.). |
| 112 | + |
| 113 | +```java |
| 114 | +// Global combine |
| 115 | +input.apply(Combine.globally(Sum.ofIntegers())); |
| 116 | + |
| 117 | +// Per-key combine |
| 118 | +input.apply(Combine.perKey(Sum.ofIntegers())); |
| 119 | +``` |
| 120 | + |
| 121 | +### Flatten |
| 122 | +Merges multiple PCollections. |
| 123 | + |
| 124 | +```java |
| 125 | +PCollectionList<String> collections = PCollectionList.of(pc1).and(pc2).and(pc3); |
| 126 | +PCollection<String> merged = collections.apply(Flatten.pCollections()); |
| 127 | +``` |
| 128 | + |
| 129 | +### Partition |
| 130 | +Splits a PCollection into multiple PCollections. |
| 131 | + |
| 132 | +## Windowing |
| 133 | + |
| 134 | +### Types |
| 135 | +- **Fixed Windows** - Regular, non-overlapping intervals |
| 136 | +- **Sliding Windows** - Overlapping intervals |
| 137 | +- **Session Windows** - Gaps of inactivity define boundaries |
| 138 | +- **Global Window** - All elements in one window (default) |
| 139 | + |
| 140 | +```java |
| 141 | +input.apply(Window.into(FixedWindows.of(Duration.standardMinutes(5)))); |
| 142 | +``` |
| 143 | + |
| 144 | +```python |
| 145 | +input | beam.WindowInto(beam.window.FixedWindows(300)) |
| 146 | +``` |
| 147 | + |
| 148 | +## Triggers |
| 149 | +Control when results are emitted. |
| 150 | + |
| 151 | +```java |
| 152 | +input.apply(Window.<T>into(FixedWindows.of(Duration.standardMinutes(5))) |
| 153 | + .triggering(AfterWatermark.pastEndOfWindow() |
| 154 | + .withEarlyFirings(AfterProcessingTime.pastFirstElementInPane() |
| 155 | + .plusDelayOf(Duration.standardMinutes(1)))) |
| 156 | + .withAllowedLateness(Duration.standardHours(1)) |
| 157 | + .accumulatingFiredPanes()); |
| 158 | +``` |
| 159 | + |
| 160 | +## Side Inputs |
| 161 | +Additional inputs to ParDo. |
| 162 | + |
| 163 | +```java |
| 164 | +PCollectionView<Map<String, String>> sideInput = |
| 165 | + lookupTable.apply(View.asMap()); |
| 166 | + |
| 167 | +mainInput.apply(ParDo.of(new DoFn<String, String>() { |
| 168 | + @ProcessElement |
| 169 | + public void processElement(ProcessContext c) { |
| 170 | + Map<String, String> lookup = c.sideInput(sideInput); |
| 171 | + // Use lookup... |
| 172 | + } |
| 173 | +}).withSideInputs(sideInput)); |
| 174 | +``` |
| 175 | + |
| 176 | +## Pipeline Options |
| 177 | +Configure pipeline execution. |
| 178 | + |
| 179 | +```java |
| 180 | +public interface MyOptions extends PipelineOptions { |
| 181 | + @Description("Input file") |
| 182 | + @Required |
| 183 | + String getInput(); |
| 184 | + void setInput(String value); |
| 185 | +} |
| 186 | + |
| 187 | +MyOptions options = PipelineOptionsFactory.fromArgs(args).as(MyOptions.class); |
| 188 | +``` |
| 189 | + |
| 190 | +## Schema |
| 191 | +Strongly-typed access to structured data. |
| 192 | + |
| 193 | +```java |
| 194 | +@DefaultSchema(AutoValueSchema.class) |
| 195 | +@AutoValue |
| 196 | +public abstract class User { |
| 197 | + public abstract String getName(); |
| 198 | + public abstract int getAge(); |
| 199 | +} |
| 200 | + |
| 201 | +PCollection<User> users = ...; |
| 202 | +PCollection<Row> rows = users.apply(Convert.toRows()); |
| 203 | +``` |
| 204 | + |
| 205 | +## Error Handling |
| 206 | + |
| 207 | +### Dead Letter Queue Pattern |
| 208 | +```java |
| 209 | +TupleTag<String> successTag = new TupleTag<>() {}; |
| 210 | +TupleTag<String> failureTag = new TupleTag<>() {}; |
| 211 | + |
| 212 | +PCollectionTuple results = input.apply(ParDo.of(new DoFn<String, String>() { |
| 213 | + @ProcessElement |
| 214 | + public void processElement(ProcessContext c) { |
| 215 | + try { |
| 216 | + c.output(process(c.element())); |
| 217 | + } catch (Exception e) { |
| 218 | + c.output(failureTag, c.element()); |
| 219 | + } |
| 220 | + } |
| 221 | +}).withOutputTags(successTag, TupleTagList.of(failureTag))); |
| 222 | + |
| 223 | +results.get(successTag).apply(WriteToSuccess()); |
| 224 | +results.get(failureTag).apply(WriteToDeadLetter()); |
| 225 | +``` |
| 226 | + |
| 227 | +## Cross-Language Pipelines |
| 228 | +Use transforms from other SDKs. |
| 229 | + |
| 230 | +```python |
| 231 | +# Use Java Kafka connector from Python |
| 232 | +from apache_beam.io.kafka import ReadFromKafka |
| 233 | + |
| 234 | +result = pipeline | ReadFromKafka( |
| 235 | + consumer_config={'bootstrap.servers': 'localhost:9092'}, |
| 236 | + topics=['my-topic'] |
| 237 | +) |
| 238 | +``` |
| 239 | + |
| 240 | +## Best Practices |
| 241 | +1. **Prefer built-in transforms** over custom DoFns |
| 242 | +2. **Use schemas** for type-safe operations |
| 243 | +3. **Minimize side inputs** for performance |
| 244 | +4. **Handle late data** explicitly |
| 245 | +5. **Test with DirectRunner** before deploying |
| 246 | +6. **Use TestPipeline** for unit tests |
0 commit comments