blob: 0757ac9ac213116b4b981f50fe169beed79a32dc [file] [view]
---
id: functions-develop-state
title: Configure state storage
sidebar_label: "Configure state storage"
description: Configure state storage for functions in Pulsar.
---
````mdx-code-block
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
````
Pulsar Functions use [Apache BookKeeper](https://bookkeeper.apache.org) as a state storage interface. Pulsar integrates with BookKeeper [table service](https://docs.google.com/document/d/155xAwWv5IdOitHh1NVMEwCMGgB28M3FyMiQSxEpjE-Y/edit#heading=h.56rbh52koe3f) to store state for functions. For example, a `WordCount` function can store the state of its counters into BookKeeper table service via [State APIs](#call-state-apis).
States are key-value pairs, where a key is a string and its value is arbitrary binary data - counters are stored as 64-bit big-endian binary values. Keys are scoped to an individual function and shared between instances of that function.
:::note
State storage is **not** available for Go functions.
:::
## Call state APIs
Pulsar Functions expose APIs for mutating and accessing `state`. These APIs are available in the [Context](functions-concepts.md#context) object when you use [Java/Python SDK](functions-develop-api.md) to develop functions.
The following table outlines the states that can be accessed within Java and Python functions.
| State-related API | Java | Python |
|-----------------------------------------|----------------------------------------|----------------|
| [Increment counter](#increment-counter) | `incrCounter` <br />`incrCounterAsync` | `incr_counter` |
| [Retrieve counter](#retrieve-counter) | `getCounter` <br />`getCounterAsync` | `get_counter` |
| [Update state](#update-state) | `putState` <br />`putStateAsync` | `put_state` |
| [Retrieve state](#retrieve-state) | `getState` <br />`getStateAsync` | `get_state` |
| [Delete state](#delete-state) | `deleteState` | `del_counter` |
## Increment counter
You can use `incrCounter` to increment the counter of a given `key` by the given `amount`.
If the `key` does not exist, a new key is created.
````mdx-code-block
<Tabs groupId="lang-choice"
defaultValue="Java"
values={[{"label":"Java","value":"Java"},{"label":"Python","value":"Python"}]}>
<TabItem value="Java">
```java
/**
* Increment the built-in distributed counter referred by key
* @param key The name of the key
* @param amount The amount to be incremented
*/
void incrCounter(String key, long amount);
```
To asynchronously increment the counter, you can use `incrCounterAsync`.
```java
/**
* Increment the built-in distributed counter referred by key
* but dont wait for the completion of the increment operation
*
* @param key The name of the key
* @param amount The amount to be incremented
*/
CompletableFuture<Void> incrCounterAsync(String key, long amount);
```
</TabItem>
<TabItem value="Python">
```python
def incr_counter(self, key, amount):
"""incr the counter of a given key in the managed state"""
```
</TabItem>
</Tabs>
````
### Retrieve counter
You can use `getCounter` to retrieve the counter of a given `key` mutated by `incrCounter`.
````mdx-code-block
<Tabs groupId="lang-choice"
defaultValue="Java"
values={[{"label":"Java","value":"Java"},{"label":"Python","value":"Python"}]}>
<TabItem value="Java">
```java
/**
* Retrieve the counter value for the key.
*
* @param key name of the key
* @return the amount of the counter value for this key
*/
long getCounter(String key);
```
To asynchronously retrieve the counter mutated by `incrCounterAsync`, you can use `getCounterAsync`.
```java
/**
* Retrieve the counter value for the key, but don't wait
* for the operation to be completed
*
* @param key name of the key
* @return the amount of the counter value for this key
*/
CompletableFuture<Long> getCounterAsync(String key);
```
</TabItem>
<TabItem value="Python">
```python
def get_counter(self, key):
"""get the counter of a given key in the managed state"""
```
</TabItem>
</Tabs>
````
### Update state
Besides the `counter` API, Pulsar also exposes a general key/value API for functions to store and update the state of a given `key`.
````mdx-code-block
<Tabs groupId="lang-choice"
defaultValue="Java"
values={[{"label":"Java","value":"Java"},{"label":"Python","value":"Python"}]}>
<TabItem value="Java">
```java
/**
* Update the state value for the key.
*
* @param key name of the key
* @param value state value of the key
*/
void putState(String key, ByteBuffer value);
```
To asynchronously update the state of a given `key`, you can use `putStateAsync`.
```java
/**
* Update the state value for the key, but don't wait for the operation to be completed
*
* @param key name of the key
* @param value state value of the key
*/
CompletableFuture<Void> putStateAsync(String key, ByteBuffer value);
```
</TabItem>
<TabItem value="Python">
```python
def put_state(self, key, value):
"""update the value of a given key in the managed state"""
```
</TabItem>
</Tabs>
````
### Retrieve state
You can use `getState` to retrieve the state of a given `key`.
````mdx-code-block
<Tabs groupId="lang-choice"
defaultValue="Java"
values={[{"label":"Java","value":"Java"},{"label":"Python","value":"Python"}]}>
<TabItem value="Java">
```java
/**
* Retrieve the state value for the key.
*
* @param key name of the key
* @return the state value for the key.
*/
ByteBuffer getState(String key);
```
To asynchronously retrieve the state of a given `key`, you can use `getStateAsync`.
```java
/**
* Retrieve the state value for the key, but don't wait for the operation to be completed
*
* @param key name of the key
* @return the state value for the key.
*/
CompletableFuture<ByteBuffer> getStateAsync(String key);
```
</TabItem>
<TabItem value="Python">
```python
def get_state(self, key):
"""get the value of a given key in the managed state"""
```
</TabItem>
</Tabs>
````
### Delete state
:::note
Both counters and binary values share the same keyspace, so this API deletes either type.
:::
````mdx-code-block
<Tabs groupId="lang-choice"
defaultValue="Java"
values={[{"label":"Java","value":"Java"}]}>
<TabItem value="Java">
```java
/**
* Delete the state value for the key.
*
* @param key name of the key
*/
void deleteState(String key);
```
</TabItem>
</Tabs>
````
## Query state via CLI
Besides using the [State APIs](#call-state-apis) to store the state of functions in Pulsar's state storage and retrieve it back from the storage, you can use CLI commands to query the state of functions.
```bash
bin/pulsar-admin functions querystate \
--tenant <tenant> \
--namespace <namespace> \
--name <function-name> \
--state-storage-url <bookkeeper-service-url> \
--key <state-key> \
[---watch]
```
If `--watch` is specified, the CLI tool keeps running to get the latest value of the provided `state-key`.
## Example
The example of `WordCountFunction` demonstrates how `state` is stored within Pulsar Functions.
````mdx-code-block
<Tabs groupId="lang-choice"
defaultValue="Java"
values={[{"label":"Java","value":"Java"},{"label":"Python","value":"Python"}]}>
<TabItem value="Java">
The logic of {@inject:github:WordCountFunction:/pulsar-functions/java-examples/src/main/java/org/apache/pulsar/functions/api/examples/WordCountFunction.java} is simple and straightforward:
1. The function splits the received `String` into multiple words using regex `\\.`.
2. For each `word`, the function increments `counter` by 1 via `incrCounter(key, amount)`.
```java
import org.apache.pulsar.functions.api.Context;
import org.apache.pulsar.functions.api.Function;
import java.util.Arrays;
public class WordCountFunction implements Function<String, Void> {
@Override
public Void process(String input, Context context) throws Exception {
Arrays.asList(input.split("\\.")).forEach(word -> context.incrCounter(word, 1));
return null;
}
}
```
</TabItem>
<TabItem value="Python">
The logic of this `WordCount` function is simple and straightforward:
1. The function first splits the received string into multiple words.
2. For each `word`, the function increments `counter` by 1 via `incr_counter(key, amount)`.
```python
from pulsar import Function
class WordCount(Function):
def process(self, item, context):
for word in item.split():
context.incr_counter(word, 1)
```
</TabItem>
</Tabs>
````