😇

Cloud PubSubのメッセージのPublish処理するCloud Functions(Node.js)がメモリエラーで落ちる

2024/02/15に公開

概要

  • Cloud Functions上であるサービスからリクエストを受け取って、そのデータをCloud PubSubへメッセージをPublishしていた
  • 4日に1度あるかないかでメモリエラーがログに出力されて落ちる
  • それの改善の話

結論

  • リクエスト頻度が多いと、Cloud Functionsのインスタンスが落ちず常駐する
  • Cloud PubSub Clientのインスタンス化をキャッシュしておかないと、リクエスト単位で毎回インスタンス化してたらメモリ解放せず圧迫し続けて落ちる

実際のエラーメッセージ

Memory limit of 512 MiB exceeded with 512 MiB used. Consider increasing the memory limit, see https://cloud.google.com/functions/docs/configuring/memory

メモリのグラフ

  • 緑枠: たまたまリクエストがなく、インスタンスがアイドルから削除されてメモリ解放された
  • 青枠: 512MBで制限しているため、超えるとこのタイミングでインスタンスが落ちて、エラーメッセージが表示される(このグラフでは実際に落ちたかどうかはわからないけども。あくまで参考とさせてください)
  • 赤枠: 修正リリース。その後はメモリ使用量が増加していないのがわかる

コードの修正

import * as functions from 'firebase-functions'
import * as express from 'express'
import { PubSub } from '@google-cloud/pubsub'

const app: express.Express = express()
const router: express.Router = express.Router()
app.use(router)

+// CFインスタンスが使い回されるため、キャッシュしておく
+const pubSubClient = new PubSub()
router.post('/api', async (req, res) => {
  // ...なんか色々やっている...
  const data = ... // リクエストからもらったデータ
  try {
    const messageId = await publisher(JSON.stringify(data))
    res.json({ message: `Messages ${messageId} published.` })
  } catch (error) {
    console.error(error)
    res.status(500).json({ error_message: 'Internal Server Error' })
  }
})

const publisher = async (data: string): Promise<string> => {
  const firebaseConfig = JSON.parse(process.env.FIREBASE_CONFIG || '{}')
  const topicName = `projects/${firebaseConfig.projectId}/topics/${process.env.TOPIC_NAME}`
- const pubSubClient = new PubSub()
  const messageId = await pubSubClient.topic(topicName).publishMessage({ data: Buffer.from(data) })
  return messageId
}

export const publisher = functions
  .runWith({
    timeoutSeconds: 120,
    memory: '512MB',
    maxInstances: 10
  })
  .https
  .onRequest(app)

Cloud Functionsのインスタンスが使い回されると、状態を引き継いで利用することができるので、それをキャッシュとして、リクエストごとに無駄なインスタンス化を削減しています

https://cloud.google.com/functions/docs/bestpractices/tips?hl=ja#use_global_variables_to_reuse_objects_in_future_invocations

個人的には解放されると思ってたんだけど、それについては引続き調査

雑感

  • インスタンス台数を10台と指定してたので、うまくさばけると思ってたら、別の所が問題になってショボーンしてた
  • メモリ解放されると思ってたんですが、インスタンスが常駐することも考慮した上でロジック組もうねって気持ちになりました。。。
CBcloud Tech Blog

Discussion