📘 บทที่ 15 — Time-Travel: ฟีเจอร์ที่ Chroma ไม่มี¶
LanceDB เก็บ ทุก version ของข้อมูล (Lance columnar format) → ย้อนดูอดีตได้ สำคัญกับ second brain: undo การลบผิด, audit "ตอนนั้นรู้อะไร", reproduce ผลงานวิจัยเก่า
In [1]:
import sys
IN_COLAB='google.colab' in sys.modules
if IN_COLAB:
%pip -q install lancedb sentence-transformers
import lancedb, shutil
import numpy as np
print('lancedb', lancedb.__version__, '✓')
lancedb 0.34.0 ✓
In [2]:
# ---- embed อัจฉริยะ (Ollama ในเครื่อง / sentence-transformers บน Colab) ----
import urllib.request, json
import numpy as np
def _ok():
try:
urllib.request.urlopen('http://localhost:11434/api/tags', timeout=2); return True
except Exception: return False
if _ok():
def embed_texts(t):
req=urllib.request.Request('http://localhost:11434/api/embed',data=json.dumps({'model':'bge-m3','input':list(t)}).encode(),headers={'Content-Type':'application/json'})
with urllib.request.urlopen(req,timeout=180) as r: V=np.array(json.load(r)['embeddings'])
return V/np.linalg.norm(V,axis=1,keepdims=True)
print('embed: Ollama bge-m3 ✓')
else:
from sentence_transformers import SentenceTransformer
_m=SentenceTransformer('BAAI/bge-m3')
def embed_texts(t): return _m.encode(list(t),normalize_embeddings=True)
print('embed: sentence-transformers ✓')
embed: Ollama bge-m3 ✓
1) สร้าง table แล้วแก้ไขหลายครั้ง — แต่ละครั้ง = version ใหม่¶
In [3]:
shutil.rmtree('./lance_tt', ignore_errors=True)
db = lancedb.connect('./lance_tt')
V = embed_texts(['โน้ตแรก งานวิจัย', 'โน้ตสอง ประชุม'])
tbl = db.create_table('notes', data=[{'id':'n1','text':'โน้ตแรก งานวิจัย','vector':V[0].tolist()},
{'id':'n2','text':'โน้ตสอง ประชุม','vector':V[1].tolist()}])
print('v1: สร้าง 2 โน้ต →', tbl.count_rows(), 'rows')
V3 = embed_texts(['โน้ตสาม สูตรกาแฟ'])
tbl.add([{'id':'n3','text':'โน้ตสาม สูตรกาแฟ','vector':V3[0].tolist()}])
print('v2: เพิ่มโน้ต →', tbl.count_rows(), 'rows')
tbl.delete("id = 'n1'")
print('v3: ลบ n1 (สมมติลบผิด!) →', tbl.count_rows(), 'rows')
v1: สร้าง 2 โน้ต → 2 rows
v2: เพิ่มโน้ต → 3 rows v3: ลบ n1 (สมมติลบผิด!) → 2 rows
2) ⭐ ย้อนเวลา — ดู version เก่า¶
In [4]:
vers = tbl.list_versions()
print(f'มีทั้งหมด {len(vers)} version')
for v in vers:
tbl.checkout(v['version'])
print(f" version {v['version']}: {tbl.count_rows()} rows")
tbl.checkout_latest()
มีทั้งหมด 3 version version 1: 2 rows version 2: 3 rows version 3: 2 rows
3) ⭐ กู้คืน — restore ข้อมูลที่ลบผิด (undo!)¶
In [5]:
# n1 หายไปใน latest
tbl.checkout_latest()
print('latest ตอนนี้:', sorted(tbl.to_pandas()['id'].tolist()), '(n1 หาย)')
# ย้อนไป version ก่อนลบ (v2 = 3 rows) แล้ว restore
v_before_delete = [v['version'] for v in vers if True][1] # v2
tbl.checkout(v_before_delete)
tbl.restore() # ทำให้ version นี้เป็น latest
print('หลัง restore v2:', sorted(tbl.to_pandas()['id'].tolist()), '← n1 กลับมา! 🎉')
latest ตอนนี้: ['n2', 'n3'] (n1 หาย) หลัง restore v2: ['n1', 'n2', 'n3'] ← n1 กลับมา! 🎉
✅ วัดผลตัวเอง #15¶
In [6]:
ids = sorted(tbl.to_pandas()['id'].tolist())
assert 'n1' in ids, 'n1 ต้องถูกกู้กลับมา'
assert len(tbl.list_versions()) >= 4, 'ต้องมี version history'
print('✅ ผ่าน! time-travel: ทุกการแก้ไข = version · ย้อนดู/กู้คืนได้')
print(' → นี่คือเหตุผลใหญ่ที่ production เลือก LanceDB — Chroma ทำแบบนี้ไม่ได้')
✅ ผ่าน! time-travel: ทุกการแก้ไข = version · ย้อนดู/กู้คืนได้ → นี่คือเหตุผลใหญ่ที่ production เลือก LanceDB — Chroma ทำแบบนี้ไม่ได้
📊 เห็นภาพ — row count แต่ละ version (ลบ→restore)¶
In [7]:
# @@VIZ@@
import matplotlib, matplotlib.pyplot as plt
from matplotlib import font_manager as _fm
_av={f.name for f in _fm.fontManager.ttflist}
for _f in ['Thonburi','Loma','Sarabun','Noto Sans Thai','TH Sarabun New']:
if _f in _av: matplotlib.rcParams['font.family']=_f; break
matplotlib.rcParams['axes.unicode_minus']=False
tbl.checkout_latest(); vers=tbl.list_versions(); counts=[]
for v in vers:
tbl.checkout(v['version']); counts.append(tbl.count_rows())
tbl.checkout_latest()
plt.figure(figsize=(7,3.6))
plt.step(range(1,len(counts)+1),counts,where='mid',color='#2a78d6',lw=2.5,marker='o',markersize=8)
plt.xticks(range(1,len(counts)+1),[f"v{v['version']}" for v in vers])
plt.ylabel('จำนวน row'); plt.grid(alpha=.3)
plt.title('บทที่ 15 — time-travel: เพิ่ม→ลบ→restore (row กลับมา)'); plt.tight_layout(); plt.show()
/var/folders/lf/s98q6y892sl__0brv1tyn0ch0000gn/T/ipykernel_17603/1755451118.py:16: UserWarning: Glyph 8594 (\N{RIGHTWARDS ARROW}) missing from font(s) Thonburi.
plt.title('บทที่ 15 — time-travel: เพิ่ม→ลบ→restore (row กลับมา)'); plt.tight_layout(); plt.show()
/opt/Code/github.com/nat-build-with-oracle/ajfon-oracle/book/.venv/lib/python3.12/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 8594 (\N{RIGHTWARDS ARROW}) missing from font(s) Thonburi.
fig.canvas.print_figure(bytes_io, **kw)
🎓 จบภาค Production¶
| ฟีเจอร์ | เรียนจาก | production ใช้ |
|---|---|---|
| embedding + cosine | Chroma บท 1–5 | เหมือนกัน (ความรู้ติดตัว) |
| hybrid FTS+vector | เขียนเอง บท 7 | LanceDB native บท 14 |
| ingest idempotent | Chroma บท 8 | เหมือนกัน |
| versioning | — | LanceDB บท 15 |
สรุป: เรียนบน Chroma (ง่าย, เห็นทุกกลไก) → ย้าย LanceDB (production) ได้ทันที เพราะแนวคิดเดียวกัน
ทฤษฎีลึก: deep-technical Ch45 (versioning), Ch64 (MVCC/WAL)