วิธีติดตั้ง OpenAnalytics บน VPS ด้วยตนเองแบบครบถ้วน
เรียนรู้วิธีติดตั้ง OpenAnalytics บน VPS ด้วยตัวเอง พร้อมข้อกำหนดทรัพยากรจริงที่ RAM 4 GB และพื้นที่ดิสก์ 25 GB รวมถึงการตั้งค่า ClickHouse, Postgres และ Valkey
ข้อกำหนดเบื้องต้นก่อนเริ่มขั้นตอนแรก
ในการโฮสต์ OpenAnalytics ด้วยตนเอง คุณต้องมี Linux VPS ที่มี RAM ประมาณ 4 GB, พื้นที่ดิสก์ว่าง 25 GB, Docker พร้อมปลั๊กอิน Compose และระเบียน DNS สี่รายการที่ชี้มายังเซิร์ฟเวอร์นี้แล้ว นี่คือข้อกำหนดที่แท้จริง ซึ่งควรทราบก่อนเริ่มใช้คำสั่งแรกแทนที่จะเป็นหลังจากนั้น
สแต็กนี้ประกอบด้วยบริการแอปพลิเคชัน 6 รายการและที่เก็บข้อมูล 3 รายการ Postgres ทำหน้าที่เป็น control plane สำหรับเก็บข้อมูลบัญชีผู้ใช้, เว็บไซต์, API keys และลิงก์แชร์ ClickHouse ทำหน้าที่เก็บข้อมูลเหตุการณ์ดิบและข้อมูลสรุปที่แดชบอร์ดใช้แสดงผล Valkey ทำงาน 2 อินสแตนซ์ โดยอินสแตนซ์หนึ่งเป็นคิวเหตุการณ์แบบถาวร และอีกอินสแตนซ์หนึ่งเป็นแคชที่ระบบยอมรับการสูญเสียข้อมูลได้ เนื่องจากทั้งสองงานนี้ต้องการนโยบายการลบข้อมูล (eviction policy) ที่แตกต่างกัน มีเพียงกระบวนการเดียวคือ query gateway เท่านั้นที่ได้รับอนุญาตให้อ่านข้อมูลจาก ClickHouse และกระบวนการนี้จะตรวจสอบลายเซ็น Ed25519 บนทุก query envelope ก่อนที่จะประมวลผล
หากสิ่งที่คุณต้องการคือไฟล์ไบนารีเดียวและไฟล์คอนฟิกเดียว นี่ไม่ใช่สิ่งที่คุณกำลังมองหา GoatCounter เป็นตัวเลือกแบบไบนารีเดี่ยวในหมวดหมู่นี้ ซึ่งประกอบด้วยไฟล์ปฏิบัติการ Go หนึ่งไฟล์, ใช้ SQLite เป็นค่าเริ่มต้น และไม่มีฐานข้อมูลภายนอกเลย สแต็กที่หนักกว่านี้จะช่วยให้คุณใช้งานฟีเจอร์ funnel, web vitals, การระบุแหล่งที่มาของรายได้จากบัญชี Stripe ของคุณเอง และเซิร์ฟเวอร์ MCP (model context protocol) ได้ การเลือกระหว่างเครื่องมือวิเคราะห์ข้อมูลแบบโฮสต์เอง คือบทความที่เปรียบเทียบข้อดีข้อเสียเหล่านั้น คู่มือนี้ถือว่าคุณได้ตัดสินใจเลือกแล้ว
ชี้ระเบียน DNS ทั้งสี่รายการมาที่เซิร์ฟเวอร์ก่อน
ซับโดเมนทั้งสี่รายการต้องชี้มาที่ IP สาธารณะของเซิร์ฟเวอร์ก่อนที่คุณจะเริ่มดำเนินการใดๆ เนื่องจาก Caddy จะร้องขอใบรับรองจาก Let's Encrypt ในการเปิดใช้งานครั้งแรก และการตรวจสอบสิทธิ์จะล้มเหลวหากชื่อโดเมนยังไม่สามารถแก้ไขเป็น IP ได้
app.example.comให้บริการแดชบอร์ดapi.example.comให้บริการ API และ OAuth callbacksc.example.comให้บริการตัวรวบรวมข้อมูลและสคริปต์ติดตามrt.example.comให้บริการสตรีมแบบเรียลไทม์
ให้ใช้ระเบียน A จำนวนสี่รายการ หรือใช้ระเบียน A หนึ่งรายการและ CNAME อีกสามรายการชี้ไปยังระเบียนนั้น ตรวจสอบความถูกต้องด้วย dig +short app.example.com ก่อนดำเนินการต่อ ชื่อโดเมนที่คุณเพิ่งเพิ่มเมื่อสักครู่อาจยังถูกแคชเป็น NXDOMAIN โดยตัวแก้ไข DNS ที่ Let's Encrypt ใช้งานอยู่ ดังนั้นหากการขอใบรับรองครั้งแรกล้มเหลว ควรเว้นระยะเวลารอและตรวจสอบ log ของ Caddy การรันตัวติดตั้งซ้ำจะไม่ช่วยให้ DNS เผยแพร่ข้อมูลได้เร็วขึ้น
วิธีการโฮสต์ OpenAnalytics ด้วยตนเองโดยใช้ Docker Compose
ให้ตรวจสอบเวอร์ชันที่ระบุไว้ในแท็ก (tagged release) สาขาเริ่มต้น (default branch) เป็นพื้นที่สำหรับการพัฒนา ส่วนแท็กของรุ่นที่ปล่อยออกมาจะตรงกับอิมเมจที่เผยแพร่จริง คำสั่งด้านล่างนี้ตั้งสมมติฐานว่าคุณได้ติดตั้ง Docker และปลั๊กอิน Compose เรียบร้อยแล้ว ซึ่งครอบคลุมอยู่ใน การรันบริการ Docker Compose บน VPS
git clone https://github.com/OpenLabs-so/openanalytics
cd openanalytics
git checkout "$(git tag -l 'v*' --sort=-v:refname | sed '/-/d' | head -1)"
cd infra/selfhost
./generate-secrets.sh --domain example.com --email you@example.com --with-geoip
docker compose pull && docker compose up -dคำสั่ง sed '/-/d' ในบรรทัดการ checkout จะละเว้นแท็กที่เป็นรุ่นทดสอบ เพื่อให้คุณได้ใช้งานเวอร์ชันเสถียรล่าสุดแทนที่จะเป็นรุ่น release candidate ส่วน --with-geoip จะดึงฐานข้อมูลเมืองจาก DB-IP ในระหว่างการสร้าง หากข้ามขั้นตอนนี้ไป เหตุการณ์ทุกอย่างจะไม่มีข้อมูลประเทศ (null country) ทำให้มุมมองทางภูมิศาสตร์ไม่แสดงผลใดๆ คุณสามารถเพิ่มข้อมูลนี้ในภายหลังได้โดยรัน infra/selfhost/geoip/fetch-dbip.sh, ตั้งค่า GEOIP_DB_PATH=/geoip/dbip-city-lite.mmdb ใน env/collector.env จากนั้นสร้าง collector ขึ้นมาใหม่ด้วย docker compose up -d --force-recreate collector ฐานข้อมูลดังกล่าวจะมีการอัปเดตทุกเดือน ดังนั้นควรทำซ้ำขั้นตอนการดึงข้อมูลทุกเดือน มิฉะนั้นข้อมูลเมืองของคุณจะล้าสมัย
สำรองข้อมูลความลับที่สร้างขึ้นก่อนดำเนินการต่อ
ตัวสร้างข้อมูลจะเขียนไฟล์ออกมา 3 ส่วน .env เก็บชื่อโดเมนและอ้างอิงรูปภาพ env/*.env เก็บไฟล์ความลับแยกตามบริการ และ docker-compose.override.yml เก็บกุญแจ Ed25519 จำนวน 3 คู่ในรูปแบบ YAML block scalars เนื่องจากไฟล์ PEM แบบหลายบรรทัดไม่สามารถอยู่ในไฟล์ env ได้ ข้อมูลทั้งหมดนี้ถูกกำหนดให้ git-ignored และไม่สามารถสร้างค่าเดิมขึ้นมาใหม่ได้
ให้คัดลอกไฟล์เหล่านั้นออกจากเครื่องทันที การสูญเสียแต่ละส่วนมีผลกระทบเฉพาะตัวดังนี้:
- หากทำรหัสผ่านของ store หาย คุณจะถูกล็อกออกจาก Postgres และ ClickHouse ซึ่งจะรีเซ็ตได้จากภายในคอนเทนเนอร์เท่านั้น
- หากทำ
OA_CREDENTIAL_KEYRINGหาย ข้อมูลรับรองของบุคคลที่สามที่เก็บไว้ทั้งหมดจะไม่สามารถกู้คืนได้ ส่งผลให้ผู้ที่เชื่อมต่อบัญชี Stripe ไว้ต้องทำการเชื่อมต่อใหม่ทั้งหมด - หากทำ
ANONYMOUS_IDENTITY_SECRETหาย ข้อมูลระบุตัวตนของผู้เข้าชมจะถูกรีเซ็ตใหม่ทั้งหมด ผู้เข้าชมจากเมื่อวานจะถูกนับเป็นผู้เข้าชมใหม่ และจะเห็นรอยต่อของข้อมูลในแผนภูมิ - หากทำ
AUTH_SECRETหาย เซสชันทั้งหมดจะถูกยกเลิก ทำให้ทุกคนต้องเข้าสู่ระบบใหม่ - หากทำกุญแจส่วนตัวสำหรับลงนาม (signing private key) หาย คุณเพียงแค่ต้องหมุนเวียนกุญแจคู่ใหม่เท่านั้น ซึ่งจะไม่มีข้อมูลใดสูญหาย
มีความลับ 2 รายการที่ต้องมีค่าไบต์เหมือนกันในไฟล์ละ 2 แห่ง ANONYMOUS_IDENTITY_SECRET ปรากฏอยู่ใน collector.env และ worker.env เนื่องจาก collector ทำหน้าที่คำนวณแฮชของผู้เข้าชมและ worker เป็นผู้เขียนค่าดังกล่าว OA_CREDENTIAL_KEYRING ปรากฏอยู่ใน api.env และ worker.env ส่วนความลับอื่นๆ ถูกจำกัดขอบเขตไว้ให้ใช้ได้เพียงบริการเดียวเท่านั้น หากบริการใดได้รับความลับที่ไม่ควรได้รับ บริการนั้นจะหยุดทำงานแทนที่จะเริ่มระบบตามปกติ
การเริ่มใช้งาน stack และการตรวจสอบ
grep OA_IMAGE .env
docker compose pull
docker compose up -d
docker compose logs -f migrate
docker compose psmigrate จะดำเนินการปรับใช้ schema ของ Postgres และ ClickHouse จากนั้นจะหยุดทำงาน ดังนั้นสถานะที่ถูกต้องเมื่อเสร็จสิ้นคือคอนเทนเนอร์ migrate หยุดทำงาน tracker-build จะคอมไพล์ oa.js ลงใน volume ที่ Caddy ให้บริการและหยุดทำงานเช่นกัน บริการอื่นทั้งหมดควรแสดงสถานะ healthy ใน docker compose ps หากบริการมีการรีสตาร์ทวนซ้ำ มักเกิดจากการตรวจสอบ environment ไม่ผ่าน โดย log จะแสดงรายการปัญหาทั้งหมดออกมาพร้อมกันแทนที่จะแสดงทีละรายการเมื่อรีสตาร์ท สาเหตุที่พบบ่อยสองประการคือการเว้นว่างตัวแปรซึ่งระบบจะปฏิเสธแทนที่จะถือว่าไม่ได้ตั้งค่า และการวาง secret ไว้ในไฟล์ service ผิดตำแหน่ง
บนสถาปัตยกรรม arm64 หรือเมื่อใช้งานจาก branch จะไม่มี image ที่เผยแพร่ไว้ คุณต้อง build ภายในเครื่องด้วย docker compose up -d --build โฮสต์ที่มีหน่วยความจำ 4 GB จะเกิดปัญหาหน่วยความจำไม่พอระหว่างการ build ให้เพิ่ม swap ก่อน ซึ่งจำเป็นต้องใช้เฉพาะในระหว่างการ build เท่านั้น:
fallocate -l 4G /swapfile && chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstabกระบวนการ build ใช้เวลาประมาณสิบนาที การดึง image ใช้เวลาเพียงไม่กี่นาที ซึ่งเป็นเหตุผลว่าทำไมจึงมี release image ไว้ให้ใช้งาน
ลงทะเบียนบัญชีแรกทันที
เปิด https://app.example.com การติดตั้งที่ยังไม่มีใครเข้าใช้งานจะไม่แสดงแบบฟอร์มลงชื่อเข้าใช้ แต่จะเสนอให้สร้างบัญชีแรก บัญชีดังกล่าวจะมีสิทธิ์ระดับสูงอย่างถาวร และเป็นบัญชีเดียวที่สามารถเข้าถึงหน้าจอการตั้งค่าการติดตั้งได้ เมื่อบัญชีนี้ถูกสร้างขึ้นแล้ว เส้นทางดังกล่าวจะตอบกลับด้วย 409 เพื่อป้องกันไม่ให้ผู้อื่นเข้ามาใช้งานต่อจากคุณได้ ให้ดำเนินการขั้นตอนนี้ทันทีที่ stack พร้อมใช้งาน อย่ารอจนถึงสัปดาห์ถัดไป
การติดตั้งตัวติดตาม (Tracker)
เพิ่มเว็บไซต์ในแดชบอร์ดแล้วระบบจะมอบแท็กให้คุณ รูปแบบของแท็กนั้นตายตัว:
<script
async
src="https://c.example.com/oa.js"
data-key="YOUR_TRACKING_KEY"
data-collector="https://c.example.com"
></script>ให้นำแท็กนี้ไปวางไว้ในส่วน head ของหน้าเว็บ คีย์สำหรับการติดตามถูกออกแบบมาให้เป็นสาธารณะอยู่แล้ว จึงสามารถวางไว้ใน HTML ที่ใครก็สามารถอ่านได้ สคริปต์จะทำการติดตั้ง window.oa และการเรียกใช้งานเช่น oa("track", ...) จะถูกจัดคิวไว้โดย stub และจะถูกส่งออกทันทีที่ไฟล์โหลดเสร็จ ดังนั้นเหตุการณ์ที่กำหนดเอง (custom event) ซึ่งถูกเรียกใช้งานตั้งแต่ช่วงต้นจะไม่สูญหาย หากมีส่วนอื่นในหน้าเว็บที่ใช้งาน window.oa อยู่แล้ว ตัวติดตามจะติดตั้งในชื่อ window.openanalytics แทน หากเว็บไซต์เดียวกันสามารถเข้าถึงได้ผ่าน onion service ให้ละเว้นการใส่แท็กนี้ใน build นั้น เพราะสคริปต์ที่ดึงมาจาก c.example.com จะดึงผู้ใช้งาน Tor Browser กลับมายัง clearnet และเชื่อมโยงที่อยู่ทั้งสองเข้าด้วยกันในการโหลดหน้าเว็บครั้งเดียว
จากนั้นให้ตรวจสอบเส้นทางทั้งหมดตั้งแต่ต้นจนจบ:
curl -s https://c.example.com/oa.js -o /dev/null -w '%{http_code} %{size_download}\n'
curl -s https://api.example.com/health | head -c 200
docker compose logs --tail=50 worker | grep -i batchคำสั่งแรกควรแสดงผลเป็น 200 และมีขนาดข้อมูลไม่กี่กิโลไบต์ ให้ลองโหลดหน้าเว็บของคุณ จากนั้นตรวจสอบบรรทัด batch ใน log ของ worker ภายในไม่กี่วินาที ตัว collector จะตอบกลับด้วย 202 ทันทีที่ได้รับเหตุการณ์ และ 202 หมายถึงเหตุการณ์ถูกจัดคิวไว้แต่ยังไม่ได้จัดเก็บ ตัว worker คือส่วนที่ทำหน้าที่ย้ายเหตุการณ์เข้าไปยัง ClickHouse หากเหตุการณ์ถูกตอบรับแล้วแต่ไม่ปรากฏในแดชบอร์ด แสดงว่า worker กำลังติดขัด ซึ่งสามารถยืนยันได้หากค่าความลึกของคิวใน Valkey เพิ่มขึ้นเรื่อยๆ สาเหตุที่พบบ่อยคือข้อมูลรับรองของ ClickHouse ใน worker.env ไม่ถูกต้อง หรือขาดสิทธิ์การเข้าถึง (grant) ในตารางที่เพิ่งถูกเพิ่มเข้ามาจากการ migration
รักษาตัวเก็บข้อมูลให้เป็นสาธารณะและวางแดชบอร์ดไว้หลังระบบยืนยันตัวตน
Caddy มาพร้อมกับไฟล์ compose และจัดการขอใบรับรองสำหรับชื่อโดเมนทั้ง 4 ชื่อด้วยตัวเอง ดังนั้นเส้นทางเริ่มต้นจึงไม่จำเป็นต้องตั้งค่า proxy เพิ่มเติม หากเซิร์ฟเวอร์ของคุณรัน nginx reverse proxy อยู่แล้ว ให้ใช้ infra/selfhost/nginx.conf.example ที่เตรียมไว้เป็นหน้าด่านแทน และคงการจัดการ header ตามเดิมไว้:
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header CF-Connecting-IP "";
proxy_set_header True-Client-IP "";
proxy_set_header Fly-Client-IP "";ตัวเก็บข้อมูลจะคำนวณค่า hash ของผู้เข้าชมรายวันจาก IP ของไคลเอนต์ ดังนั้นจึงต้องรับค่าที่อยู่ IP จากการเชื่อมต่อโดยตรงเท่านั้น ห้ามรับจาก header การส่งผ่าน CF-Connecting-IP จาก hop ที่ไม่น่าเชื่อถือจะทำให้ผู้เรียกสามารถปลอมแปลงที่อยู่ใดก็ได้ ซึ่งจะส่งผลให้ข้อมูลตำแหน่งทางภูมิศาสตร์ผิดพลาดและทำให้จำนวนผู้เข้าชมสูงเกินจริง
การเข้าถึงจะถูกแยกตามชื่อโฮสต์อย่างชัดเจน c. และ rt. ต้องสามารถเข้าถึงได้โดยผู้เข้าชมทุกคนจากทุกเว็บไซต์ที่คุณวัดผล ดังนั้นห้ามใส่ basic auth หรือ IP allowlist ไว้หน้าสองส่วนนี้ ส่วน app. และ api. ควรเข้าถึงได้เฉพาะผู้ที่ล็อกอินเท่านั้น ระบบยืนยันตัวตนของแอปพลิเคชันเองคือสิ่งที่ปกป้องแดชบอร์ด: การล็อกอินด้วยรหัสผ่านถูกเปิดใช้งานเป็นค่าเริ่มต้นผ่าน AUTH_PASSWORD_SIGNIN=enabled ใน env/api.env และปุ่ม Google หรือ GitHub จะปรากฏขึ้นก็ต่อเมื่อมีการระบุทั้ง client ID และ client secret ของผู้ให้บริการนั้นๆ ส่วน Magic links จำเป็นต้องมีระบบขนส่งอีเมล หากไม่มีระบบดังกล่าว API จะเขียนข้อมูลการส่งไปยัง outbox เท่านั้น ทำให้ไม่มีการส่งอีเมลออกไปและไม่มีข้อความแจ้งเตือนข้อผิดพลาด หากแอปพลิเคชันที่คุณ self-host อื่นๆ อยู่หลัง Authentik login เดียวกัน อยู่แล้ว ให้ตัดสินใจตั้งแต่เนิ่นๆ ว่าจะให้แดชบอร์ดนี้รวมเข้ากับระบบดังกล่าวหรือแยกบัญชีต่างหาก เพราะบัญชีแรกที่คุณสร้างที่นี่จะเป็นบัญชีที่มีสิทธิ์สูงสุดอย่างถาวร
การตั้งค่าหนึ่งอย่างจะเป็นตัวตัดสินว่าแดชบอร์ดจะทำงานได้หรือไม่ AUTH_TRUSTED_ORIGINS ใน env/api.env ต้องตรงกับ origin ของแดชบอร์ดอย่างแม่นยำ หากตั้งค่าผิดหรือหายไป API จะไม่ส่ง header CORS (cross-origin resource sharing) ทำให้เบราว์เซอร์ปฏิเสธทุกการเรียกใช้งาน และคุณจะได้แดชบอร์ดที่แสดงผลโครงสร้างหน้าเว็บได้แต่ไม่แสดงข้อมูล ในขณะที่ docker compose ps รายงานว่าทุกอย่างทำงานปกติ
ในระหว่างที่คุณตั้งค่า proxy ให้จัดการกับ traffic อัตโนมัติด้วย บอท crawl จะเข้าถึงตัวเก็บข้อมูลเช่นเดียวกับผู้ใช้ทั่วไป และยอดการเข้าชมของบอทจะถูกบันทึกลงใน ClickHouse และรวมอยู่ในสถิติของคุณ การ บล็อก AI crawlers ที่ระดับเซิร์ฟเวอร์ จะช่วยป้องกันไม่ให้ข้อมูลขยะเหล่านี้เข้าสู่ฐานข้อมูล ซึ่งจะช่วยรักษาความแม่นยำและประหยัดพื้นที่ดิสก์ของคุณได้
ความหมายของ cookieless ในบริบทนี้และสิ่งที่คุณต้องแลก
ที่นี่ไม่มีการใช้คุกกี้ ตัวตนของผู้เข้าชมจะถูกแปลงเป็น salted hash โดยที่ salt จะเปลี่ยนใหม่ทุกวัน และไม่มีการจัดเก็บ IP address แบบดิบไว้เลย ข้อมูลตำแหน่งทางภูมิศาสตร์จะถูกประมวลผลภายในเครื่องโดยเทียบกับไฟล์ DB-IP ที่อยู่ในดิสก์ของคุณ ดังนั้นข้อมูลการเข้าชมของผู้ใช้งานจึงไม่เคยถูกส่งออกไปภายนอกโฮสต์ การเก็บข้อมูลไว้ภายในเครื่องช่วยลดการพึ่งพาผู้ให้บริการภายนอก แต่ไม่ได้ลดปริมาณข้อมูล ซึ่งเป็นข้อจำกัดเดียวกับที่พบเมื่อ คุณรันอินสแตนซ์ SearXNG ของคุณเอง แล้ว IP ของเซิร์ฟเวอร์คุณกลายเป็นสิ่งที่ search engine มองเห็นแทน
สิ่งที่คุณได้รับคือการไม่มีตัวระบุตัวตนที่คงค้างอยู่ในอุปกรณ์ของผู้เข้าชม ซึ่งเป็นปัจจัยสำคัญที่ทำให้ตัวติดตามเข้าข่ายกฎระเบียบ ePrivacy ของสหภาพยุโรป การตั้งค่าที่เก็บเฉพาะข้อมูลรวม (aggregate-only) เช่นนี้จึงมักใช้งานได้โดยไม่ต้องมีแบนเนอร์ขอความยินยอมด้วยเหตุผลดังกล่าว อย่างไรก็ตาม GDPR ยังคงควบคุมสิ่งที่คุณจัดเก็บและระยะเวลาในการจัดเก็บ ซึ่งเป็นเรื่องที่ที่ปรึกษากฎหมายของคุณจะเป็นผู้ตัดสิน ไม่ใช่ไฟล์ README นี้
สิ่งที่คุณต้องแลกคือความสามารถในการระบุตัวตนข้ามวัน การหมุนเวียน salt หมายความว่าผู้ที่เข้าชมในวันจันทร์และกลับมาอีกครั้งในวันพุธจะถูกนับเป็นผู้เข้าชมสองคน ซึ่งเป็นไปตามการออกแบบและไม่มีวิธีแก้ไข จำนวนผู้เข้าชมที่ไม่ซ้ำกันรายวันนั้นมีความแม่นยำ ส่วนจำนวนผู้เข้าชมรายสัปดาห์และรายเดือนนั้นถูกคำนวณมาจากรายวัน จึงอาจทำให้ตัวเลขการเข้าถึงสูงเกินจริง ดังนั้นตัวเลข "ผู้เข้าชมที่กลับมา" ในช่วงเวลาที่ยาวนานจึงไม่ได้วัดผลตามชื่อเรียกของมัน เซสชันและเส้นทางการเข้าชมจะเชื่อถือได้เฉพาะภายในวันเดียวเท่านั้น การหมุนเวียน ANONYMOUS_IDENTITY_SECRET ส่งผลเช่นเดียวกับการเปลี่ยนผ่านของวัน ดังนั้นให้ถือว่าการหมุนเวียนนั้นเป็นการเปลี่ยนแปลงของข้อมูลมากกว่าจะเป็นเพียงการดูแลรักษาตามปกติ
ตัวเก็บข้อมูลจะเคารพการตั้งค่า Do Not Track และ Global Privacy Control ซึ่งเป็นสัญญาณจากเบราว์เซอร์ที่แจ้งเว็บไซต์ไม่ให้ขายหรือแบ่งปันข้อมูลส่วนบุคคล แท็กสคริปต์มีสวิตช์สำหรับควบคุมการทำงานในลักษณะเดียวกัน ได้แก่ data-respect-gpc, data-respect-dnt และ data-require-consent ซึ่งจะระงับการเก็บข้อมูลทั้งหมดจนกว่าจะได้รับความยินยอม และจะจดจำคำตอบไว้ใน localStorage ภายใต้คีย์ oa.consent การตั้งค่า data-storage="none" จะเป็นการปิดการจัดเก็บข้อมูลบนเบราว์เซอร์โดยสมบูรณ์
เหตุผลที่ดิสก์เต็มหลังจากใช้งานไปหกเดือน
นี่คือสาเหตุที่ทำให้เซิร์ฟเวอร์วิเคราะห์ข้อมูลแบบ self-hosted ล่ม และโดยปกติแล้วเหตุการณ์ (events) ไม่ใช่สาเหตุหลัก
ให้เริ่มดูที่อิมเมจ (images) การปล่อยซอฟต์แวร์หนึ่งครั้งจะใช้อิมเมจประมาณสิบตัว ซึ่งกินพื้นที่บนดิสก์รวมแล้วประมาณ 13 GB การอัปเกรดจะดึงอิมเมจรุ่นใหม่มาก่อนที่จะลบของเก่าทิ้ง ดังนั้นในช่วงเวลาหนึ่งคุณจะเก็บอิมเมจไว้ถึงสองรุ่น ซึ่งนั่นคือส่วนใหญ่ของพื้นที่ 25 GB ที่ต้องการ ก่อนที่จะมี page view เข้ามาแม้แต่ครั้งเดียว
จากนั้นคือเรื่องของ snapshot โดย snapshot.sh จะหยุดการทำงานของ stack, ทำการสำรองข้อมูล volume ทั้งหมดรวมถึง secret ทุกตัว แล้วจึงเริ่มการทำงานใหม่ การสำรองข้อมูลแบบ cold copy เป็นวิธีเดียวที่ปลอดภัยในกรณีนี้ เพราะ ClickHouse มีการรวมข้อมูล (merge) อยู่เบื้องหลัง และการคัดลอกข้อมูลในระหว่างที่กำลัง merge จะทำให้ข้อมูลไม่สอดคล้องกัน upgrade.sh จะทำการสำรองข้อมูลอัตโนมัติก่อนการอัปเกรดทุกครั้ง ดังนั้นไฟล์สำรองจะสะสมอยู่บนดิสก์ลูกเดิมจนกว่าคุณจะกำหนดขีดจำกัดไว้
./snapshot.sh create --label before-something-risky
./snapshot.sh list
./snapshot.sh --keep 3บนโฮสต์ที่พื้นที่ใกล้เต็ม ให้ลบอิมเมจรุ่นก่อนหน้าทิ้งก่อนทำการอัปเกรด วิธีนี้ปลอดภัยในขณะที่ stack กำลังทำงานอยู่ เพราะอิมเมจที่คอนเทนเนอร์กำลังใช้งานอยู่นั้นยังมี reference อ้างอิงอยู่:
docker image prune -a -fจากนั้นคือตัวข้อมูลเหตุการณ์ (events) เอง ClickHouse บีบอัดข้อมูลแบบ columnar ได้ดีมาก ดังนั้นปริมาณข้อมูลดิบจึงเติบโตช้ากว่าที่หลายคนคาดไว้ และตารางสรุปผล (rollup tables) ที่แดชบอร์ดอ่านก็มีขนาดเล็กเมื่อเทียบกับตารางข้อมูลดิบ ให้ใช้วิธีวัดค่าแทนการคาดเดา:
docker system df -v
docker compose exec clickhouse df -h /var/lib/clickhouseสำหรับตัวเลขแยกตามตาราง ให้รันคำสั่งนี้ด้วยข้อมูลรับรอง (credentials) ของ ClickHouse ที่ตัวสร้าง (generator) เขียนไว้ภายใต้ infra/selfhost/env/:
SELECT table, formatReadableSize(sum(bytes_on_disk)) AS size, sum(rows) AS row_count
FROM system.parts
WHERE active
GROUP BY table
ORDER BY sum(bytes_on_disk) DESC;ให้จดค่าที่วัดได้ในสัปดาห์แรกและสัปดาห์ที่สี่ ข้อมูลสองจุดนี้จะบอกอัตราการเติบโต และอัตราการเติบโตจะบอกคุณว่าเมื่อใดที่ต้องขยายขนาด volume คู่มือการใช้งานแบบ self-hosting ณ เดือนสิงหาคม 2026 ยังไม่มีการระบุถึงการตั้งค่า retention หรือ time-to-live สำหรับข้อมูลดิบ ดังนั้นให้กำหนดขนาดดิสก์ตามอัตราที่คุณวัดได้จริง แทนที่จะคาดหวังว่าแถวข้อมูลเก่าจะถูกลบออกไปเอง
มีกับดักเรื่องการลบข้อมูลหนึ่งอย่างที่ควรทราบก่อนจะเจอปัญหา การลบไซต์หรือบัญชีจะนำงานไปเข้าคิวไว้ที่ worker และ worker นั้นจำเป็นต้องมีการตั้งค่า CLICKHOUSE_MAINTENANCE_USER และ CLICKHOUSE_MAINTENANCE_PASSWORD โดยต้องมีผู้ใช้ oa_maintenance ที่ตรงกันอยู่ใน ClickHouse หากไม่มีการตั้งค่าเหล่านี้ งานลบข้อมูลจะค้างอยู่ในคิวตลอดไป ไซต์จะหายไปจากแดชบอร์ดแต่ข้อมูลทุกแถวจะยังคงอยู่บนดิสก์ ทำให้คุณเข้าใจว่าการล้างข้อมูลเสร็จสิ้นแล้ว แต่พื้นที่ดิสก์ไม่ได้ถูกคืนกลับมาเลย
การอัปเกรดและต้นทุนสามประการ
git fetch --tags
git checkout "$(git tag -l 'v*' --sort=-v:refname | sed '/-/d' | head -1)"
cd infra/selfhost
./upgrade.shupgrade.sh จะแสดงต้นทุนสามประการก่อนเริ่มดำเนินการ ช่วงเวลาที่ระบบหยุดทำงาน (downtime) เป็นเรื่องจริง: เหตุการณ์ที่เกิดขึ้นในขณะที่ตัวรวบรวมข้อมูล (collector) หยุดทำงานจะสูญหายไป เนื่องจากตัวติดตาม (tracker) ไม่มีการลองส่งข้อมูลซ้ำ การย้อนกลับ (rollback) จะทำให้ข้อมูลสูญหาย เนื่องจาก rollback.sh --to backups/<snapshot> จะแทนที่ที่เก็บข้อมูลทั้งสองแห่งทั้งหมดและทิ้งทุกแถวที่เขียนขึ้นหลังจากที่สแนปชอตนั้นถูกสร้างขึ้น ต้นทุนประการที่สามคือพื้นที่ดิสก์ ซึ่งก็คือกลุ่มของสแนปชอตที่อธิบายไว้ข้างต้น
กฎการรีสตาร์ทสองข้อที่มักทำผิดพลาดได้ง่าย ประการแรก ให้เริ่มการทำงานของ query gateway ก่อน API เนื่องจาก API เวอร์ชันใหม่จะส่งฟิลด์การสืบค้นที่ gateway เวอร์ชันเก่าไม่รองรับ ประการที่สอง ClickHouse จำเป็นต้องใช้การสร้างใหม่ (recreate) แทนการรีสตาร์ท เนื่องจาก docker compose restart จะนำสภาพแวดล้อมเดิมของคอนเทนเนอร์กลับมาใช้ใหม่และเพิกเฉยต่อการแก้ไขของคุณโดยไม่มีการแจ้งเตือน:
docker compose up -d --force-recreate clickhouseแดชบอร์ดก็มีกับดักในลักษณะเดียวกัน ต้นทาง NEXT_PUBLIC_* ทั้งสามแห่งใน env/web.env จะถูกคอมไพล์รวมเข้าไปใน bundle ของเบราว์เซอร์และถูกแทนที่เมื่อคอนเทนเนอร์เริ่มทำงาน ดังนั้นแดชบอร์ดที่เรียกใช้ชื่อโฮสต์ที่ไม่ถูกต้องจะต้องแก้ไขด้วย docker compose up -d --force-recreate web เท่านั้น ไม่ใช่ด้วย restart บันทึก (log) ของเว็บคอนเทนเนอร์จะแสดงต้นทางที่ใช้ในการเริ่มระบบ ซึ่งเป็นวิธีที่เร็วที่สุดในการยืนยันว่าการแก้ไขนั้นมีผลแล้ว
หาก ClickHouse ปฏิเสธที่จะเริ่มทำงานหลังจากแก้ไขไฟล์คอนฟิก ให้ตรวจสอบบรรทัดแรกของบันทึก บรรทัดที่ขึ้นต้นด้วย oa-entrypoint: หมายถึง entrypoint ปฏิเสธค่าที่คุณตั้งไว้ หากเป็นข้อความอื่น มักหมายความว่าไฟล์คอนฟิกเป็น XML ที่ไม่ถูกต้อง ซึ่งสาเหตุที่พบบ่อยที่สุดคือการใช้เครื่องหมายยัติภังค์คู่ (double hyphen) ภายในคอมเมนต์ของ XML ซึ่งเป็นสิ่งที่ไม่อนุญาตให้ทำในส่วนนั้น
AGPL-3.0 และชื่อโครงการ
ซอร์สโค้ดนี้อยู่ภายใต้สัญญาอนุญาต AGPL-3.0 การนำไปใช้งานโดยไม่มีการแก้ไขสำหรับเว็บไซต์ของคุณเองนั้นไม่มีข้อผูกมัดในการเผยแพร่ใดๆ ทั้งสิ้น ข้อผูกมัดจะเริ่มขึ้นเมื่อคุณแก้ไขซอร์สโค้ดและเรียกใช้งานเวอร์ชันที่แก้ไขนั้นในฐานะบริการเครือข่าย โดยสัญญาอนุญาตกำหนดให้คุณต้องเสนอซอร์สโค้ดที่แก้ไขแล้วให้แก่ผู้ใช้งานบริการนั้น ข้อกำหนดนี้ครอบคลุมถึงการให้แดชบอร์ดแก่ลูกค้าบนอินสแตนซ์ของคุณ และการรวมซอฟต์แวร์นี้เข้ากับผลิตภัณฑ์ที่คุณจำหน่าย การเก็บการเปลี่ยนแปลงของคุณไว้ใน public fork ถือว่าปฏิบัติตามข้อกำหนดนี้โดยไม่ต้องมีขั้นตอนเพิ่มเติม
ในส่วนของแบรนด์นั้นแยกออกจากซอร์สโค้ด ชื่อ "OpenAnalytics" และโดเมนที่โฮสต์โครงการระบุถึงอินสแตนซ์ที่ผู้พัฒนาเป็นผู้ดูแล ซึ่งไม่ได้เป็นส่วนหนึ่งของการอนุญาตตามสัญญาอนุญาต การติดตั้งใช้งานของคุณเป็นการรันซอฟต์แวร์โดยไม่ได้ใช้แบรนด์ดังกล่าว ดังนั้นคุณควรตั้งชื่อบริการของคุณเองก่อนที่จะนำไปให้บริการแก่ลูกค้าที่ชำระเงิน
FAQ
ฉันสามารถรัน OpenAnalytics บน VPS ขนาด 1 GB ได้หรือไม่?
ไม่ได้ โครงการนี้ต้องการ RAM ประมาณ 4 GB และพื้นที่ดิสก์ว่าง 25 GB เนื่องจากหนึ่งการติดตั้งจะรันบริการแอปพลิเคชัน 6 รายการควบคู่ไปกับ Postgres, ClickHouse และอินสแตนซ์ Valkey อีก 2 รายการ โดยเฉพาะ ClickHouse ไม่ใช่กระบวนการที่ใช้ทรัพยากรน้อย บนเครื่องขนาด 1 GB คอนเทนเนอร์จะเริ่มทำงานได้ แต่ kernel out-of-memory killer จะเข้ามาจัดการคอนเทนเนอร์ตัวใดตัวหนึ่ง ซึ่งมักจะเป็น ClickHouse หากแผน 1 GB เป็นข้อจำกัดที่เลี่ยงไม่ได้ ให้ใช้เครื่องมือที่เป็น single-binary เช่น GoatCounter ซึ่งรันบน SQLite โดยไม่ต้องมีฐานข้อมูลภายนอก
ฉันจำเป็นต้องมีแบนเนอร์คุกกี้สำหรับ OpenAnalytics หรือไม่?
นั่นเป็นคำถามสำหรับทนายความของคุณ แต่ข้อเท็จจริงทางเทคนิคเป็นประโยชน์ต่อคุณ เนื่องจากไม่มีการใช้คุกกี้ ตัวตนของผู้เข้าชมคือ salted hash ที่หมุนเวียนทุกวัน และไม่มีการจัดเก็บ IP address แบบดิบ ดังนั้นจึงไม่มีการบันทึกข้อมูลถาวรเพื่อระบุตัวตนของผู้เข้าชม อย่างไรก็ตาม GDPR ยังคงควบคุมสิ่งที่คุณจัดเก็บและระยะเวลาที่คุณเก็บข้อมูลไว้ หากคุณต้องการให้มีการเก็บข้อมูลโดยได้รับความยินยอมอย่างชัดเจน ให้ตั้งค่า data-require-consent บนแท็กสคริปต์ ตัวติดตามจะไม่เก็บข้อมูลใดๆ จนกว่าจะได้รับความยินยอม และจะเก็บคำตอบไว้ใน localStorage ภายใต้ oa.consent
ทำไมเหตุการณ์ต่างๆ ถึงส่งสถานะ 202 กลับมา แต่ไม่ปรากฏในแดชบอร์ด?
202 หมายความว่าตัวรวบรวมข้อมูลยอมรับและนำเหตุการณ์เข้าคิวแล้ว ไม่ได้หมายความว่าได้จัดเก็บลงฐานข้อมูลแล้ว ตัว worker จะทำหน้าที่ดึงข้อมูลจากคิวไปใส่ใน ClickHouse ดังนั้นหากแดชบอร์ดว่างเปล่าทั้งที่คำขอสำเร็จ ปัญหาจะอยู่ที่ตัว worker ให้ตรวจสอบ docker compose logs --tail=50 worker และดูความลึกของคิวใน Valkey หากคิวมีขนาดเพิ่มขึ้นเรื่อยๆ แสดงว่า worker ถูกบล็อก สาเหตุที่พบบ่อยคือข้อมูลรับรอง ClickHouse ใน worker.env ไม่ถูกต้อง หรือขาดสิทธิ์การเข้าถึงตารางที่สร้างขึ้นจากการย้ายข้อมูล (migration) ล่าสุด
ทำไมแดชบอร์ดถึงว่างเปล่าในขณะที่คอนเทนเนอร์ทุกตัวมีสถานะปกติ?
ให้ตรวจสอบ AUTH_TRUSTED_ORIGINS ใน env/api.env ก่อน ค่านี้ต้องตรงกับ origin ของแดชบอร์ดอย่างแม่นยำ หากไม่ตรงกัน API จะไม่ส่ง CORS headers ทำให้เบราว์เซอร์ปฏิเสธทุกคำขอ คุณจึงเห็นเลย์เอาต์ที่ทำงานได้แต่ไม่มีข้อมูล สิ่งที่สองที่ต้องตรวจสอบคือค่า NEXT_PUBLIC_* ทั้งสามรายการใน env/web.env ซึ่งจะถูกแทนที่เมื่อคอนเทนเนอร์เว็บเริ่มทำงาน การแก้ไขค่าเหล่านี้จำเป็นต้องใช้ docker compose up -d --force-recreate web เพราะการรีสตาร์ทแบบปกติจะยังคงใช้ค่าเดิมอยู่
AGPL-3.0 ห้ามไม่ให้ฉันนำบริการนี้ไปเสนอให้ลูกค้าหรือไม่?
ไม่ห้าม โดยมีเงื่อนไขเดียวคือ หากคุณรันโค้ดโดยไม่มีการแก้ไข คุณก็ไม่มีภาระผูกพันใดๆ ต่อใคร แต่หากคุณแก้ไขโค้ดและรันเวอร์ชันที่แก้ไขนั้นเป็นบริการที่ผู้อื่นใช้งาน คุณต้องเปิดเผยซอร์สโค้ดที่คุณแก้ไขให้ผู้ใช้เหล่านั้นทราบ ซึ่งการทำ public fork ก็ถือว่าเพียงพอแล้ว นอกจากนี้ ชื่อ "OpenAnalytics" ไม่ได้ถูกอนุญาตให้ใช้ร่วมกับโค้ด ดังนั้นสิ่งใดก็ตามที่คุณนำไปขายจำเป็นต้องใช้ชื่อของตนเอง