Skip to content

Storage Wars: CacheService vs. PropertiesService vs. Firestore Benchmarks

Published on Markdown

Update: See Exploring Apps Script CacheService Limits for a deep dive into CacheService behavior and limits.

In Google Apps Script, there are a few options for key-value stores. This post will cover the following options:

Here is a quick comparison of the options:

OptionItem SizeItemsCostExpirationAccess Control
PropertiesService9KB1000FreeNoYes (Separate Properties)
CacheService1KB1000FreeYesYes (Separate Caches)
Firestore1GBunlimitedPay as you goYesYes (Rules)
Sheet Developer Metadata2MB?FreeNoNo

You might have noticed I left off a key element here, latency, because I want cover that under below.

Latency Comparisons

The latency of each option is the deciding factor for high-performance scripts. I ran a benchmark script (source below) performing 100 sequential write/read operations of a 100-byte payload.

StoreAvg Latency (Read+Write)Speed Factor
CacheService~63 ms1x (Baseline)
PropertiesService~80 ms1.25x Slower
Firestore (REST)~350 ms5.5x Slower
SpreadsheetApp~800+ ms12x Slower

The Takeaway:

  • CacheService is faster, but not by the order-of-magnitude some expect. Use it for data that must expire.
  • PropertiesService is surprisingly performant for persistent storage, clocking in just behind CacheService.
  • SpreadsheetApp remains the bottleneck. Avoid using it as a database at all costs.
JavaScript
JavaScript
function runBenchmark() {
  const iterations = 100;
  const payload = "x".repeat(100); // 100 bytes

  // Benchmark CacheService
  const cacheFn = () => {
    const cache = CacheService.getScriptCache();
    cache.put("benchmark_test", payload, 10);
    cache.get("benchmark_test");
  };

  // Benchmark PropertiesService
  const propsFn = () => {
    const props = PropertiesService.getScriptProperties();
    props.setProperty("benchmark_test", payload);
    props.getProperty("benchmark_test");
  };

  const cacheTime = timeExecution(cacheFn, iterations);
  const propsTime = timeExecution(propsFn, iterations);

  Logger.log(`CacheService (Avg/Op): ${cacheTime.toFixed(2)}ms`); // ~15-20ms
  Logger.log(`PropertiesService (Avg/Op): ${propsTime.toFixed(2)}ms`); // ~150-200ms
}

function timeExecution(fn, iterations) {
  const start = new Date().getTime();
  for (let i = 0; i < iterations; i++) {
    fn();
  }
  const end = new Date().getTime();
  return (end - start) / iterations;
}

Open Questions

  • What happens when there is concurrent access to the key-value store?
  • What happens when the script is run in different environments (e.g., add-on, web app, API)?

Decision Guidance

Do I need expiration?

Of course these are only guideline and you can do any number of things with keys, e.g. use a key suffix to simulate a ttl.

const keyToday = `${key}-${new Date().toISOString().split("T")[0]}`;

How many items am I storing?

How large of values am I storing?

How important is access control?

How sensitive is my application to latency?

How important is cost?

Background

Below is an overview of each key-value store option in Google Apps Script.

CacheService

The CacheService in Google Apps Script provides a way to store key-value pairs in memory for a certain period of time. It offers two types of caches: User Cache and Script Cache.

  • User Cache: This cache is associated with the user running the script. It can be used to store user-specific data that needs to be accessed across different script executions.
  • Script Cache: This cache is associated with the script itself. It can be used to store script-specific data that needs to be accessed by all users running the script.

Items stored in the CacheService have a maximum size of 1KB per item and a total limit of 1000 items for each cache. When you hit this limit, you will see the following error:

Exception: Argument too large: value

To learn more about CacheService and its methods, you can refer to the official documentation.

PropertiesService

The PropertiesService in Google Apps Script provides a way to store key-value pairs. It offers three types of properties: User Properties, Script Properties, and Document Properties.

  • User Properties: PropertiesService.getUserProperties() These properties are associated with the user running the script and are stored in the user’s Google Account. They are accessible across different scripts and can be used to store user-specific data.
  • Script Properties: PropertiesService.getScriptProperties() These properties are associated with the script itself and are stored in the script project. They are accessible by all users running the script and can be used to store script-specific data.
  • Document Properties: PropertiesService.getDocumentProperties() These properties are associated with a specific document and are stored in the document itself. They are accessible by all users who have access to the document and can be used to store document-specific data.

Properties stored using PropertiesService have a maximum size of 9KB per property and a total limit of 500KB for all properties combined. When you hit this limit, you will see the following error:

Exception: You have exceeded the property storage quota.
  Please remove some properties and try again.

To match the CacheService interface I wrapped the PropertiesService in a class:

JavaScript
class PropertiesWrapper {
  /**
   * @params {Properties} properties
   */
  constructor(properties) {
    this.properties = properties;
  }
  /**
   * @params {String} k
   * @params {String} v
   */
  put(k, v) {
    this.properties.setProperties({ k: v });
  }

  /**
   * @params {String} k
   * @returns {String|undefined}
   */
  get(k) {
    return this.properties.getProperty(k);
  }
}

To learn more about PropertiesService and its methods, you can refer to the official documentation.

Sheet Developer Metadata

The Sheet Developer Metadata is a feature in Google Sheets that allows developers to store custom metadata associated with a spreadsheet. This metadata can be used to store additional information or settings related to the spreadsheet.

With Sheet Developer Metadata, developers can create and manage metadata keys and values, which can be accessed programmatically using the Google Sheets API. This provides a way to store and retrieve custom information about a spreadsheet, such as configuration settings, tracking data, or any other relevant data.

The main limitation here will be rate limits and you may see an error like this (you can request increases in quotas):

GoogleJsonResponseException: API call to sheets.spreadsheets.batchUpdate failed with error:
  Quota exceeded for quota metric 'Write requests' and limit 'Write requests per minute per
    user' of service 'sheets.googleapis.com' for consumer 'project_number:1234567890'

To learn more about Sheet Developer Metadata and its usage, you can refer to the official documentation.

Firestore

Firestore is a flexible, scalable, and fully managed NoSQL document database provided by Google Cloud. It is designed to store, sync, and query data for web, mobile, and server applications. Firestore offers real-time data synchronization, automatic scaling, and powerful querying capabilities.

With Firestore, you can store and retrieve structured data in the form of documents organized into collections. It supports a wide range of data types and provides features like transactions, indexes, and security rules for fine-grained access control. Compared to other possible stores, the free quota for Firestore should cover equivalent usage. You can refer to the pricing page for more details. Firestore now has TTL. See this blog post.

To learn more about Firestore and its features, you can refer to the official documentation or read my blog post on Using Firestore in Apps Script.

Conclusion

The choice of key-value store in Google Apps Script depends on the specific use case and requirements. Each option has its own advantages and limitations, and it’s important to consider factors like item size, item count, cost, expiration, and access control when making a decision.

Frequently Asked Questions

What are the key-value store options in Google Apps Script?

In my experience, there are four main options for key-value stores in Apps Script - PropertiesService, CacheService, Firestore, and Sheet Developer Metadata. I've compared them in this post to help you choose the right one for your project.

When should I use PropertiesService?

I use PropertiesService when I need to store a small number of items (up to 1000) that are not too large (up to 9KB). It's free and I can scope properties to the user, the script, or a specific document.

When should I use CacheService?

CacheService is my go-to for low-latency caching of small items. It's free, but I have to be aware of the 1000-item limit and the fact that the oldest items will be evicted when the cache gets full.

When should I use Firestore?

I reach for Firestore when I have a lot of data or large items. It's a 'pay as you go' service, but the free tier is very generous. It's also great for real-time data and when I need more control over access with security rules.

What is the difference between PropertiesService and CacheService?

The way I see it, PropertiesService is for stuff I need to keep around, while CacheService is for temporary data that can expire. PropertiesService can store larger items, but CacheService is faster.

How do I handle concurrent access to a key-value store?

For PropertiesService and CacheService, I use LockService to prevent problems when multiple users are trying to write at the same time. Firestore is more advanced and has built-in transactions to handle this for me.

Disclaimer: I am a member of the Google Workspace Developer Relations team. The opinions expressed here are my own and do not necessarily represent those of Google.

© 2024 by Justin Poehnelt is licensed under CC BY-SA 4.0