BigtableByteStore 통합
BigtableByteStore 통합
키-값 저장소를 안정적이고 빠른 와이드 컬럼 DB에 두고 싶을 때가 있어요. 구조화·반구조화·비구조화 데이터를 빠르게 읽고 쓰는 데 강한 백엔드가 필요하다면, Google Cloud Bigtable이 좋은 선택이 돼요. LangChain의 BigtableByteStore는 Bigtable을 키-값 저장소의 백엔드로 연결해 줘요.
Bigtable은 키-값이면서 와이드 컬럼 구조의 저장소예요. 구조화된 데이터든 반구조화든, 심지어 구조 없는 데이터든 빠른 접근에 적합하죠. BigtableByteStore는 동기·비동기 연산을 모두 지원하며, 키-값 쌍을 저장·조회·삭제할 수 있어요.
Open In Colab 버튼으로 노트북에서 바로 실행해 볼 수도 있어요.
준비하기
사전 요건
시작하려면 활성화된 Bigtable 인스턴스와 테이블이 있는 Google Cloud 프로젝트가 필요해요.
설치
통합은 langchain-google-bigtable 패키지에 들어 있어요. 아래 명령은 임베딩 캐시 예시를 위해 langchain-google-vertexai도 함께 설치해요.
pip install -qU langchain-google-bigtable langchain-google-vertexai
프로젝트 설정
노트북 안에서 사용할 Google Cloud 프로젝트를 지정해요. 프로젝트 ID를 모른다면 gcloud config list를 실행하거나 프로젝트 ID 찾기 문서를 참고하면 돼요.
# @markdown 프로젝트, 인스턴스, 테이블 정보를 채워 주세요.
PROJECT_ID = "your-gcp-project-id" # @param {type:"string"}
INSTANCE_ID = "your-instance-id" # @param {type:"string"}
TABLE_ID = "your-table-id" # @param {type:"string"}
!gcloud config set project {PROJECT_ID}
인증
프로젝트 리소스에 접근하려면 Google Cloud에 인증해야 해요. Colab에서는 아래 셀을 쓰고, Gemini Enterprise Agent Platform Workbench에서는 설정 문서를 참고해요.
from google.colab import auth
auth.authenticate_user()
만들기
BigtableByteStore를 쓰려면 먼저 테이블이 존재하는지 확인하고, 연결을 관리할 BigtableEngine을 초기화해요.
from langchain_google_bigtable import (
BigtableByteStore,
BigtableEngine,
init_key_value_store_table,
)
# 테이블과 컬럼 패밀리가 있는지 확인해요.
init_key_value_store_table(
project_id=PROJECT_ID,
instance_id=INSTANCE_ID,
table_id=TABLE_ID,
)
BigtableEngine
BigtableEngine 객체는 저장소의 실행 컨텍스트를 다뤄요. 특히 비동기 연산에서 중요하죠. 성능을 위해 엔진 하나를 초기화해서 여러 저장소에서 재사용하는 걸 권장해요.
# 비동기 연산을 관리할 엔진을 초기화해요.
engine = await BigtableEngine.async_initialize(
project_id=PROJECT_ID, instance_id=INSTANCE_ID
)
BigtableByteStore
이게 키-값 저장소와 상호작용하는 핵심 클래스예요. 데이터를 저장·조회·삭제하는 메서드를 제공하죠.
# 저장소를 초기화해요.
store = await BigtableByteStore.create(engine=engine, table_id=TABLE_ID)
사용하기
저장소는 동기(mset, mget)와 비동기(amset, amget) 메서드를 모두 지원해요. 이 가이드에서는 비동기 버전을 쓸게요.
저장
amset으로 키-값 쌍을 저장해요.
kv_pairs = [
("key1", b"value1"),
("key2", b"value2"),
("key3", b"value3"),
]
await store.amset(kv_pairs)
조회
amget으로 값을 가져와요. 키가 없으면 그 키에 대해 None이 돌아와요.
retrieved_vals = await store.amget(["key1", "key2", "nonexistent_key"])
print(retrieved_vals)
삭제
amdelete로 저장소에서 키를 제거해요.
await store.amdelete(["key3"])
# 키가 삭제됐는지 확인해요.
await store.amget(["key1", "key3"])
키 순회
ayield_keys로 모든 키 또는 특정 접두사로 시작하는 키를 순회할 수 있어요.
all_keys = [key async for key in store.ayield_keys()]
print(f"All keys: {all_keys}")
prefixed_keys = [key async for key in store.ayield_keys(prefix="key1")]
print(f"Prefixed keys: {prefixed_keys}")
고급 활용: 임베딩 캐시
키-값 저장소의 대표적인 용도는 텍스트 임베딩 계산처럼 비용이 큰 연산을 캐시해서 시간과 비용을 아끼는 거예요.
from langchain_classic.embeddings import CacheBackedEmbeddings
from langchain_google_vertexai.embeddings import VertexAIEmbeddings
underlying_embeddings = VertexAIEmbeddings(
project=PROJECT_ID, model_name="textembedding-gecko@003"
)
# 다른 데이터와 키 충돌을 피하려고 네임스페이스를 써요.
cached_embedder = CacheBackedEmbeddings.from_bytes_store(
underlying_embeddings, store, namespace="text-embeddings"
)
print("First call (computes and caches embedding):")
%time embedding_result_1 = await cached_embedder.aembed_query("Hello, world!")
print("\nSecond call (retrieves from cache):")
%time embedding_result_2 = await cached_embedder.aembed_query("Hello, world!")
첫 호출은 임베딩을 계산해 캐시하고, 두 번째 호출은 캐시에서 바로 가져오는 걸 볼 수 있어요.
간단한 문서 검색기로 쓰기
이번엔 Bigtable 저장소를 이용해 간단한 검색기(retriever)를 만드는 예시예요. 저장소를 문서 영속 계층으로 삼아, 쿼리 접두사와 일치하는 문서를 가져와요.
from langchain_core.retrievers import BaseRetriever
from langchain_core.documents import Document
from langchain_core.callbacks import CallbackManagerForRetrieverRun
from typing import List, Optional, Any, Union
import json
class SimpleKVStoreRetriever(BaseRetriever):
"""키-값 저장소에서 접두사 일치로 문서를 가져오는 간단한 검색기예요."""
store: BigtableByteStore
documents: List[Union[Document, str]]
k: int
def set_up_store(self):
kv_pairs_to_set = []
for i, doc in enumerate(self.documents):
if isinstance(doc, str):
doc = Document(page_content=doc)
if not doc.id:
doc.id = str(i)
value = (
"Page Content\n"
+ doc.page_content
+ "\nMetadata"
+ json.dumps(doc.metadata)
)
kv_pairs_to_set.append((doc.id, value.encode("utf-8")))
self.store.mset(kv_pairs_to_set)
async def _aget_relevant_documents(
self,
query: str,
*,
run_manager: Optional[CallbackManagerForRetrieverRun] = None,
) -> List[Document]:
keys = [key async for key in self.store.ayield_keys(prefix=query)][: self.k]
documents_retrieved = []
async for document in await self.store.amget(keys):
if document:
document_str = document.decode("utf-8")
page_content = document_str.split("Content\n")[1].split("\nMetadata")[0]
metadata = json.loads(document_str.split("\nMetadata")[1])
documents_retrieved.append(
Document(page_content=page_content, metadata=metadata)
)
return documents_retrieved
def _get_relevant_documents(
self,
query: str,
*,
run_manager: Optional[CallbackManagerForRetrieverRun] = None,
) -> list[Document]:
keys = [key for key in self.store.yield_keys(prefix=query)][: self.k]
documents_retrieved = []
for document in self.store.mget(keys):
if document:
document_str = document.decode("utf-8")
page_content = document_str.split("Content\n")[1].split("\nMetadata")[0]
metadata = json.loads(document_str.split("\nMetadata")[1])
documents_retrieved.append(
Document(page_content=page_content, metadata=metadata)
)
return documents_retrieved
documents = [
Document(
page_content="Goldfish are popular pets for beginners, requiring relatively simple care.",
metadata={"type": "fish", "trait": "low maintenance"},
id="fish#Goldfish",
),
Document(
page_content="Cats are independent pets that often enjoy their own space.",
metadata={"type": "cat", "trait": "independence"},
id="mammals#Cats",
),
Document(
page_content="Rabbits are social animals that need plenty of space to hop around.",
metadata={"type": "rabbit", "trait": "social"},
id="mammals#Rabbits",
),
]
retriever_store = BigtableByteStore.create_sync(
engine=engine, instance_id=INSTANCE_ID, table_id=TABLE_ID
)
KVDocumentRetriever = SimpleKVStoreRetriever(
store=retriever_store, documents=documents, k=2
)
KVDocumentRetriever.set_up_store()
KVDocumentRetriever.invoke("fish")
KVDocumentRetriever.invoke("mammals")
물고기(fish)를 조회하면 해당 접두사와 일치하는 문서가, 동물(mammals)을 조회하면 그에 맞는 문서가 돌아오는 것을 확인할 수 있어요.
API 레퍼런스
BigtableByteStore 클래스에 대한 자세한 내용은 GitHub의 소스 코드에서 확인할 수 있어요.