AWS LambdaのSQLiteバージョンが古い問題の解消

ここ最近SQLiteが自分の観測範囲(twitter)で盛り上がりを見せています。

SQLiteには全文検索機能のFTS(Full Text Search) Extensionがあり、AWS Lambda 上で動くサーバーレスな全文検索エンジンとして構築しようとしたのですが、タイトルにある通りバージョン関係でハマったのでそのエラーと解消についての記事になります。

(*SQLite日本語全文検索についてはまた別の機会に記事にしようと思いますしました。 → SQLite FTS : trigram tokenizerでunigram&bigram検索までサポート

AWS LambdaのSQLiteバージョンが古い

ローカル開発環境にインストール済みのSQliteのバージョンは3.31.1 です

$ sqlite3 --version
3.31.1

今回Pythonで開発し, SQLite データベースに対する DB-APIを持つモジュール sqlite3 を用います。

$ python
import sqlite3
sqlite3.sqlite_version
'3.31.1'

一旦この環境下で作成保存した全文検索用データベースファイル fts.db をS3バケットにアップロードして、 lambda関数からGetして全文検索を行います。

FTS用に作成したlambda関数[sqlite-func]にて
・lambda_function.py

import json
import boto3
import sqlite3

import os
import datetime
import subprocess
import zipfile

s3 = boto3.resource('s3')

# バケット名,オブジェクト名
BUCKET_NAME = 'sqlite-sample'
OBJECT_KEY_NAME = "fts.db.zip"  #全文検索.dbはサイズ大きくなりがち s3 > lambdaの転送速度を稼ぐために圧縮してs3に保存しておく

#.db.の保存先 lambda内で読み書き可能な/tmp/配下へ
file_path = '/tmp/' + OBJECT_KEY_NAME

def lambda_handler(event, context):

    try:
        if os.path.isfile(file_path) ==True:
            pass
        else:
            bucket = s3.Bucket(BUCKET_NAME)
            bucket.download_file(OBJECT_KEY_NAME,  file_path)
        
            zp = zipfile.ZipFile(file_path, "r")
            zp.extractall(path='/tmp/')
            zp.close()
        
        conn = sqlite3.connect("/tmp/fts.db")
        c = conn.cursor()
        
        #確認 テーブル一覧の取得
        sql = "SELECT name FROM sqlite_master WHERE type='table'"
        c.execute(sql)

        result =c.fetchall()
        print(result)


        #全文検索  fts テーブルから 山田を探す
        sql = "SELECT * FROM fts WHERE text MATCH '山田'  order by bm25(fts) LIMIT 5 "
        c.execute(sql)
        
        result =c.fetchall()
        print(result)
        
        c.close()

        return json.dumps(result)
    
    except Exception as e:
        print(e)

テストするとエラー

#出力
malformed database schema (fts_config) - near "WITHOUT": syntax error

色々調べた結果、タイトル通りどうやらlambda内バージョンと持ち込んだ.dbファイルとでバージョン違いで読み込めずエラーとなっていた模様。

lambda内のSQliteバージョンを確認します。ランタイムはPython3.8 , Python3.9。
・lambda_function.py

import sqlite3

def lambda_handler(event, context):
    print(sqlite3)
    print(sqlite3.sqlite_version)
    return

#出力
<module 'sqlite3' from '/var/lang/lib/python3.8/sqlite3/__init__.py'>
3.7.17

古い、、SQLiteのリリースノートを見ると ver 3.7.17 は 2013-05-20 公開とのこと。
3.7.17はFTSのバージョンがまだFTS3 or FTS4で、今回ローカル環境下でFTS5で.db作成したためにエラーとなったようです。

なおFTS5から検索クエリにAND/OR/NOTが使えるようになったりクエリヒットした文中箇所の位置情報が取得できるようになったりと色々便利になっているので、FTS3/4にダウンさせる対応はせずに別の解消を目指します。

その後調査してAmazonLinuxへのデプロイでDockerFile内で最新SQLiteをダウンロードしなおしてという記事もありましたが、もう少し手軽にできる方法を模索してpysqlite3-binaryをみつけました

pysqlite3-binary

(Deeplにて翻訳)

このライブラリは、Python 3 の SQLite モジュールを、個別にインストール可能なモジュールとしてパッケージングしたものです。
これは、(amalgamation オプションによって) 他のバージョンの SQLite で動作する SQLite モジュールを作成するのに便利でしょう。

完全に自己完結したバイナリパッケージ (wheel) が、バージョン 0.4.1 以降の pysqlite3-binary として提供されています。このパッケージには、多数の拡張機能を持つ SQLite の最新リリースが含まれており、外部への依存は必要ありません。

ということで、今回これを利用します。

pysqlite3-binaryをlambdaへ適用

手順としてはlambda関数のlayerにアップロードしてlambda関数から利用できるようにします

#ローカル環境にて
$ mkdir package_for_layer

# 指定ディレクトリ以下へインストール
$ pip install pysqlite3-binary -t ./package_for_layer

#layerアップロードの為に固める
$ zip -r package_for_layer.zip  ./package_for_layer/

#確認
$ tree -L 2

#出力
.
├── package_for_layer
│   ├── example.db
│   ├── pysqlite3
│   ├── pysqlite3-binary
│   ├── pysqlite3_binary-0.4.6.dist-info
│   └── pysqlite3_binary.libs
└── package_for_layer.zip

zipの準備できたのでawsへアップロードします。
GUIコンパネからでもいいですし、今回は aws cli でやってみます。

$ aws lambda publish-layer-version \
--layer-name test-layer-pysqlite \
--zip-file fileb://package_for_layer.zip \
--compatible-runtimes python3.8

#出力
{
    "Content": {
        "Location": "https://awslambda-ap-ne-1-layers.s3.ap-northeast-1.amazonaws.com/snapshots/XXXXXXXXXXXX/test-layer-pysqlite-59b9fdd6-0e93-4941-8942-66268564381a?versionId=g.31cZ7Bzqrd7zydSMK8Ff7ceSVIRZFW&X-Amz-Security-Token=XXXXXXXXXXXXXXXXXXXXXXXX&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20220627T005208Z&X-Amz-SignedHeaders=host&X-Amz-Expires=599&X-Amz-Credential=ASIA5MMZC4DJRYIHU7B6%2F20220627%2Fap-northeast-1%2Fs3%2Faws4_request&X-Amz-Signature=XXXXXXXXXXXXXXXXXXXXXXXX",
        "CodeSha256": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
        "CodeSize": 5338291
    },
    "LayerArn": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:layer:test-layer-pysqlite",
    "LayerVersionArn": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:layer:test-layer-pysqlite:1",
    "Description": "",
    "CreatedDate": "2022-06-27T00:52:12.725+0000",
    "Version": 1,
    "CompatibleRuntimes": [
        "python3.8"
    ]
}

layerアップロード成功しました。
作成済みlambda関数の[test-func]にむけてlayerを適用します

$ aws lambda update-function-configuration \
--function-name test-func \
--layers arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:layer:test-layer-pysqlite:1

#出力
{
    "FunctionName": "test-func",
    "FunctionArn": "arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:test-func",
    "Runtime": "python3.8",
    "Role": "arn:aws:iam::xxxxxxxxxxxx:role/service-role/test-func-role-p62pt8v3",
    "Handler": "lambda_function.lambda_handler",
    "CodeSize": 894,
    "Description": "",
    "Timeout": 3,
    "MemorySize": 3048,
    "LastModified": "2022-06-27T03:09:57.000+0000",
    "CodeSha256": "9Eu7tycH910fvk8MufRxUcsfK5Eg86A0UzWfu5Rb0TY=",
    "Version": "$LATEST",
    "TracingConfig": {
        "Mode": "PassThrough"
    },
    "RevisionId": "176c4b46-c101-4ceb-bc3c-698ef209983a",
    "Layers": [
        {
            "Arn": "arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:layer:test-layer-pysqlite:1",
            "CodeSize": 5338291
        }
    ],
    "State": "Active",
    "LastUpdateStatus": "InProgress",
    "LastUpdateStatusReason": "The function is being created.",
    "LastUpdateStatusReasonCode": "Creating",
    "PackageType": "Zip"
}

layerの適用成功しました。

後はsqlite3からでなくpysqlite3-binaryのSQLiteを呼び出すように一か所だけコードを修正します。


[変更前] 
import sqlite3

[変更後] 
import pysqlite3 as sqlite3 #layerから直接利用可能に

from pysqlite3 import dbapi2 as sqlite3 #この記述でも同様に可能です

バージョン確認用のlambda関数にて
・lambda_function.py

import pysqlite3 as sqlite3

def lambda_handler(event, context):
    print(sqlite3)
    print(sqlite3.sqlite_version)
    return

#出力
<module 'pysqlite3.dbapi2' from '/opt/python/lib/python3.8/site-packages/pysqlite3/dbapi2.py'>
3.38.5

最新バージョンになりました。

まとめ

AWS LambdaのSQLiteバージョンが古い問題について、Lambda layerからpysqlite3-binaryを当ててlambda関数から最新のSQLiteバージョンを利用できるようになりました。

SQliteFTS5での日本語全文検索についてはまた別途記事にしたいと思います。それでは。

(参考)
install-newer-version-of-sqlite3-on-aws-lambda
how-to-use-the-latest-sqlite3-version-in-python
SQLite3のFTSについて概要を抑える
Amazon Linux 2でSQLite3を最新バージョンにする
AWS LambdaでAWS CLIから関数にLayerを追加・削除する方法

最新ブログ一覧