2016-01-18 19 views
25

Tôi có một cơ sở dữ liệu đại diện cho siêu dữ liệu của camera NVR bảo mật. Có một hàng recording 26 byte cho mỗi đoạn video 1 phút. (Nếu bạn tò mò, tài liệu thiết kế đang được tiến hành here.) Giới hạn thiết kế của tôi là 8 máy ảnh, 1 năm (~ 4 triệu hàng, nửa triệu trên mỗi máy ảnh). Tôi đã giả lập một số dữ liệu để kiểm tra hiệu suất. Truy vấn này chậm hơn tôi mong đợi:Truy vấn SQLite này có thể thực hiện nhanh hơn nhiều không?

select 
    recording.start_time_90k, 
    recording.duration_90k, 
    recording.video_samples, 
    recording.sample_file_bytes, 
    recording.video_sample_entry_id 
from 
    recording 
where 
    camera_id = ? 
order by 
    recording.start_time_90k; 

Đó chỉ là quét tất cả dữ liệu cho máy ảnh, sử dụng chỉ mục để lọc ra các máy ảnh và thứ tự khác. Index trông như thế này:

create index recording_camera_start on recording (camera_id, start_time_90k); 

explain query plan trông như mong đợi:

0|0|0|SEARCH TABLE recording USING INDEX recording_camera_start (camera_id=?) 

Các hàng là khá nhỏ.

$ sqlite3_analyzer duplicated.db 
... 

*** Table RECORDING w/o any indices ******************************************* 

Percentage of total database...................... 66.3% 
Number of entries................................. 4225560 
Bytes of storage consumed......................... 143418368 
Bytes of payload.................................. 109333605 76.2% 
B-tree depth...................................... 4 
Average payload per entry......................... 25.87 
Average unused bytes per entry.................... 0.99 
Average fanout.................................... 94.00 
Non-sequential pages.............................. 1   0.0% 
Maximum payload per entry......................... 26 
Entries that use overflow......................... 0   0.0% 
Index pages used.................................. 1488 
Primary pages used................................ 138569 
Overflow pages used............................... 0 
Total pages used.................................. 140057 
Unused bytes on index pages....................... 188317  12.4% 
Unused bytes on primary pages..................... 3987216  2.8% 
Unused bytes on overflow pages.................... 0 
Unused bytes on all pages......................... 4175533  2.9% 

*** Index RECORDING_CAMERA_START of table RECORDING *************************** 

Percentage of total database...................... 33.7% 
Number of entries................................. 4155718 
Bytes of storage consumed......................... 73003008 
Bytes of payload.................................. 58596767 80.3% 
B-tree depth...................................... 4 
Average payload per entry......................... 14.10 
Average unused bytes per entry.................... 0.21 
Average fanout.................................... 49.00 
Non-sequential pages.............................. 1   0.001% 
Maximum payload per entry......................... 14 
Entries that use overflow......................... 0   0.0% 
Index pages used.................................. 1449 
Primary pages used................................ 69843 
Overflow pages used............................... 0 
Total pages used.................................. 71292 
Unused bytes on index pages....................... 8463   0.57% 
Unused bytes on primary pages..................... 865598  1.2% 
Unused bytes on overflow pages.................... 0 
Unused bytes on all pages......................... 874061  1.2% 

... 

Tôi muốn một cái gì đó như thế này (có thể chỉ một tháng tại một thời điểm, chứ không phải một năm) để chạy mỗi khi một trang web cụ thể bị truy cập, vì vậy tôi muốn nó khá nhanh. Nhưng trên máy tính xách tay của tôi, nó mất hầu hết một giây, và trên Raspberry Pi 2 tôi muốn hỗ trợ, đó là cách quá chậm. Thời gian (tính bằng giây) bên dưới; đó là CPU-ràng buộc (người dùng + sys thời gian ~ = thời gian thực):

laptop$ time ./bench-profiled 
trial 0: time 0.633 sec 
trial 1: time 0.636 sec 
trial 2: time 0.639 sec 
trial 3: time 0.679 sec 
trial 4: time 0.649 sec 
trial 5: time 0.642 sec 
trial 6: time 0.609 sec 
trial 7: time 0.640 sec 
trial 8: time 0.666 sec 
trial 9: time 0.715 sec 
... 
PROFILE: interrupts/evictions/bytes = 1974/489/72648 

real 0m20.546s 
user 0m16.564s 
sys  0m3.976s 
(This is Ubuntu 15.10, SQLITE_VERSION says "3.8.11.1") 

raspberrypi2$ time ./bench-profiled 
trial 0: time 6.334 sec 
trial 1: time 6.216 sec 
trial 2: time 6.364 sec 
trial 3: time 6.412 sec 
trial 4: time 6.398 sec 
trial 5: time 6.389 sec 
trial 6: time 6.395 sec 
trial 7: time 6.424 sec 
trial 8: time 6.391 sec 
trial 9: time 6.396 sec 
... 
PROFILE: interrupts/evictions/bytes = 19066/2585/43124 

real 3m20.083s 
user 2m47.120s 
sys 0m30.620s 
(This is Raspbian Jessie; SQLITE_VERSION says "3.8.7.1") 

Tôi có thể sẽ làm một số loại dữ liệu không chuẩn hóa, nhưng trước tiên tôi muốn xem liệu tôi có thể nhận được truy vấn đơn giản này để thực hiện tốt nhất có thể. Điểm chuẩn của tôi khá đơn giản; nó chuẩn bị báo cáo trước và sau đó lặp lại điều này:

void Trial(sqlite3_stmt *stmt) { 
    int ret; 
    while ((ret = sqlite3_step(stmt)) == SQLITE_ROW) ; 
    if (ret != SQLITE_DONE) { 
    errx(1, "sqlite3_step: %d (%s)", ret, sqlite3_errstr(ret)); 
    } 
    ret = sqlite3_reset(stmt); 
    if (ret != SQLITE_OK) { 
    errx(1, "sqlite3_reset: %d (%s)", ret, sqlite3_errstr(ret)); 
    } 
} 

Tôi đã lập hồ sơ CPU với gperftools. Hình ảnh:

CPU profile graph

$ google-pprof bench-profiled timing.pprof 
Using local file bench-profiled. 
Using local file timing.pprof. 
Welcome to pprof! For help, type 'help'. 
(pprof) top 10 
Total: 593 samples 
    154 26.0% 26.0%  377 63.6% sqlite3_randomness 
    134 22.6% 48.6%  557 93.9% sqlite3_reset 
     83 14.0% 62.6%  83 14.0% __read_nocancel 
     61 10.3% 72.8%  61 10.3% sqlite3_strnicmp 
     41 6.9% 79.8%  46 7.8% sqlite3_free_table 
     26 4.4% 84.1%  26 4.4% sqlite3_uri_parameter 
     25 4.2% 88.4%  25 4.2% llseek 
     13 2.2% 90.6%  121 20.4% sqlite3_db_config 
     12 2.0% 92.6%  12 2.0% __pthread_mutex_unlock_usercnt (inline) 
     10 1.7% 94.3%  10 1.7% __GI___pthread_mutex_lock 

này trông đủ lạ để cho tôi hy vọng nó có thể được cải thiện. Có lẽ tôi đang làm một cái gì đó câm. Tôi đặc biệt hoài nghi về hoạt động sqlite3_randomnesssqlite3_strnicmp:

  • tài liệu nói sqlite3_randomness được sử dụng để chèn rowids trong một số trường hợp, nhưng tôi chỉ làm một truy vấn chọn. Tại sao nó sẽ sử dụng nó ngay bây giờ? Từ mã nguồn sqlite3 skimming, tôi thấy nó được sử dụng trong lựa chọn cho sqlite3ColumnsFromExprList nhưng điều đó có vẻ là một cái gì đó sẽ xảy ra khi chuẩn bị tuyên bố. Tôi đang làm điều đó một lần, không phải trong phần được điểm chuẩn.
  • strnicmp dùng để so sánh chuỗi phân biệt chữ hoa chữ thường. Nhưng mọi trường trong bảng này là một số nguyên. Tại sao nó sẽ sử dụng chức năng này? Nó so sánh cái gì?
  • và nói chung, tôi không biết tại sao sqlite3_reset sẽ tốn kém hoặc lý do tại sao nó được gọi từ sqlite3_step.

Schema:

-- Each row represents a single recorded segment of video. 
-- Segments are typically ~60 seconds; never more than 5 minutes. 
-- Each row should have a matching recording_detail row. 
create table recording (
    id integer primary key, 
    camera_id integer references camera (id) not null, 

    sample_file_bytes integer not null check (sample_file_bytes > 0), 

    -- The starting time of the recording, in 90 kHz units since 
    -- 1970-01-01 00:00:00 UTC. 
    start_time_90k integer not null check (start_time_90k >= 0), 

    -- The duration of the recording, in 90 kHz units. 
    duration_90k integer not null 
     check (duration_90k >= 0 and duration_90k < 5*60*90000), 

    video_samples integer not null check (video_samples > 0), 
    video_sync_samples integer not null check (video_samples > 0), 
    video_sample_entry_id integer references video_sample_entry (id) 
); 

Tôi đã quét hắc ín lên dữ liệu thử nghiệm + chương trình thử nghiệm của tôi; bạn có thể tải về nó here.


Sửa 1:

Ahh, nhìn qua mã SQLite, tôi thấy một đầu mối:

int sqlite3_step(sqlite3_stmt *pStmt){ 
    int rc = SQLITE_OK;  /* Result from sqlite3Step() */ 
    int rc2 = SQLITE_OK;  /* Result from sqlite3Reprepare() */ 
    Vdbe *v = (Vdbe*)pStmt; /* the prepared statement */ 
    int cnt = 0;    /* Counter to prevent infinite loop of reprepares */ 
    sqlite3 *db;    /* The database connection */ 

    if(vdbeSafetyNotNull(v)){ 
    return SQLITE_MISUSE_BKPT; 
    } 
    db = v->db; 
    sqlite3_mutex_enter(db->mutex); 
    v->doingRerun = 0; 
    while((rc = sqlite3Step(v))==SQLITE_SCHEMA 
     && cnt++ < SQLITE_MAX_SCHEMA_RETRY){ 
    int savedPc = v->pc; 
    rc2 = rc = sqlite3Reprepare(v); 
    if(rc!=SQLITE_OK) break; 
    sqlite3_reset(pStmt); 
    if(savedPc>=0) v->doingRerun = 1; 
    assert(v->expired==0); 
    } 

Dường như sqlite3_step cuộc gọi sqlite3_reset về biến đổi giản đồ. (FAQ entry) Tôi không biết tại sao có muốn được một sự thay đổi schema kể từ khi tuyên bố của tôi đã được chuẩn bị dù ...


Chỉnh sửa 2:

Tôi đã tải về SQLite 3.10.1 "amalgation "và biên soạn với nó với các biểu tượng gỡ lỗi. Tôi nhận được một hồ sơ khá khác nhau bây giờ mà không có vẻ lạ, nhưng nó không phải là bất kỳ nhanh hơn. Có lẽ những kết quả kì lạ mà tôi đã thấy trước đây là do Code Identity Folding hay cái gì đó.

enter image description here


Sửa 3:

Cố gắng giải pháp chỉ số clustered Ben dưới đây, nó là khoảng 3.6x nhanh hơn. Tôi nghĩ đây là cách tốt nhất tôi sẽ làm với truy vấn này. Hiệu suất CPU của SQLite là khoảng ~ 700 MB/s trên máy tính xách tay của tôi. Viết tắt nó để sử dụng một trình biên dịch JIT cho máy ảo của nó hoặc một số như vậy, tôi sẽ không làm gì tốt hơn. Đặc biệt, tôi nghĩ những lời kêu gọi kỳ lạ mà tôi thấy trong hồ sơ đầu tiên của mình không thực sự xảy ra; gcc phải viết thông tin gỡ lỗi gây hiểu lầm do tối ưu hóa hoặc một cái gì đó. Ngay cả khi hiệu suất của CPU được cải thiện, thông lượng đó lớn hơn dung lượng lưu trữ của tôi có thể làm lạnh khi đọc và tôi nghĩ điều này cũng đúng trên Pi (trong đó có USB 2.0 có giới hạn cho thẻ SD). .

$ time ./bench 
sqlite3 version: 3.10.1 
trial 0: realtime 0.172 sec cputime 0.172 sec 
trial 1: realtime 0.172 sec cputime 0.172 sec 
trial 2: realtime 0.175 sec cputime 0.175 sec 
trial 3: realtime 0.173 sec cputime 0.173 sec 
trial 4: realtime 0.182 sec cputime 0.182 sec 
trial 5: realtime 0.187 sec cputime 0.187 sec 
trial 6: realtime 0.173 sec cputime 0.173 sec 
trial 7: realtime 0.185 sec cputime 0.185 sec 
trial 8: realtime 0.190 sec cputime 0.190 sec 
trial 9: realtime 0.192 sec cputime 0.192 sec 
trial 10: realtime 0.191 sec cputime 0.191 sec 
trial 11: realtime 0.188 sec cputime 0.188 sec 
trial 12: realtime 0.186 sec cputime 0.186 sec 
trial 13: realtime 0.179 sec cputime 0.179 sec 
trial 14: realtime 0.179 sec cputime 0.179 sec 
trial 15: realtime 0.188 sec cputime 0.188 sec 
trial 16: realtime 0.178 sec cputime 0.178 sec 
trial 17: realtime 0.175 sec cputime 0.175 sec 
trial 18: realtime 0.182 sec cputime 0.182 sec 
trial 19: realtime 0.178 sec cputime 0.178 sec 
trial 20: realtime 0.189 sec cputime 0.189 sec 
trial 21: realtime 0.191 sec cputime 0.191 sec 
trial 22: realtime 0.179 sec cputime 0.179 sec 
trial 23: realtime 0.185 sec cputime 0.185 sec 
trial 24: realtime 0.190 sec cputime 0.190 sec 
trial 25: realtime 0.189 sec cputime 0.189 sec 
trial 26: realtime 0.182 sec cputime 0.182 sec 
trial 27: realtime 0.176 sec cputime 0.176 sec 
trial 28: realtime 0.173 sec cputime 0.173 sec 
trial 29: realtime 0.181 sec cputime 0.181 sec 
PROFILE: interrupts/evictions/bytes = 547/178/24592 

real 0m5.651s 
user 0m5.292s 
sys  0m0.356s 

Tôi có thể phải giữ một số dữ liệu không chuẩn hóa. May mắn thay, tôi nghĩ rằng tôi chỉ có thể giữ nó trong RAM của ứng dụng của tôi cho rằng nó sẽ không được quá lớn, khởi động không phải là đáng ngạc nhiên nhanh chóng, và chỉ có một quá trình bao giờ viết vào cơ sở dữ liệu.

+3

Cảm ơn bạn đã đặt rất nhiều nỗ lực nghiên cứu vào câu hỏi của bạn! Bạn có thể nói cho dù bạn là CPU ràng buộc hoặc IO-ràng buộc? Bạn đang sử dụng [Thẻ SD Class 10 trên Raspberry Pi của bạn] (http://raspberrypi.stackexchange.com/q/12191/27703)? –

+2

Cảm ơn! Và một câu hỏi quan trọng tôi quên trả lời. Đó là CPU-ràng buộc trên cả hai hệ thống. Tôi đã thêm đầu ra "thời gian" ở trên để hiển thị điều này. Và tôi đang sử dụng thẻ SD Class 10: http://www.amazon.com/gp/product/B010Q588D4?psc=1&redirect=true&ref_=od_aui_detailpages00 –

+2

Câu hỏi thú vị! Với mức độ chi tiết này, bạn có lẽ cũng nên đăng bài cho người dùng sqlite ML. – viraptor

Trả lời

2

Bạn cần chỉ mục nhóm hoặc nếu bạn đang sử dụng phiên bản SQLite không hỗ trợ một chỉ mục bao gồm.

Sqlite 3.8.2 trở lên

Sử dụng này trong SQLite 3.8.2 trở lên:

create table recording (
    camera_id integer references camera (id) not null, 

    sample_file_bytes integer not null check (sample_file_bytes > 0), 

    -- The starting time of the recording, in 90 kHz units since 
    -- 1970-01-01 00:00:00 UTC. 
    start_time_90k integer not null check (start_time_90k >= 0), 

    -- The duration of the recording, in 90 kHz units. 
    duration_90k integer not null 
     check (duration_90k >= 0 and duration_90k < 5*60*90000), 

    video_samples integer not null check (video_samples > 0), 
    video_sync_samples integer not null check (video_samples > 0), 
    video_sample_entry_id integer references video_sample_entry (id), 

    --- here is the magic 
    primary key (camera_id, start_time_90k) 
) WITHOUT ROWID; 

Đầu phiên bản

Trong các phiên bản trước đó của SQLite bạn có thể sử dụng này loại điều để tạo ra một chỉ số bao gồm.Điều này sẽ cho phép SQLite để kéo các giá trị dữ liệu từ các chỉ số, tránh lấy một trang riêng biệt cho mỗi hàng:

create index recording_camera_start on recording (
    camera_id, start_time_90k, 
    sample_file_bytes, duration_90k, video_samples, video_sync_samples, video_sample_entry_id 
); 

Thảo luận

Chi phí có khả năng là IO (không phân biệt mà bạn nói nó không phải) vì nhớ lại rằng IO yêu cầu CPU vì dữ liệu phải được sao chép vào và ra khỏi bus.

Nếu không có chỉ mục nhóm, các hàng được chèn bằng hàng và có thể không theo bất kỳ thứ tự hợp lý nào. Điều đó có nghĩa là đối với mỗi hàng 26 byte mà bạn yêu cầu, hệ thống có thể phải lấy một trang 4KB từ thẻ SD - đó là rất nhiều chi phí.

Với giới hạn 8 camera, chỉ mục nhóm đơn giản trên id để đảm bảo chúng xuất hiện trên đĩa theo thứ tự được chèn có thể cung cấp cho bạn tốc độ tăng gấp 10 lần bằng cách đảm bảo rằng trang đã tìm nạp chứa 10-20 hàng tiếp theo được yêu cầu.

Chỉ mục nhóm trên cả máy ảnh và thời gian phải đảm bảo rằng mỗi trang được tìm nạp có chứa 100 hàng trở lên.

+0

Cảm ơn! Thú vị giải pháp, và tôi chỉ chuẩn nó ở trên; nhanh hơn 3 lần. 'camera_id, start_time_90k' có thể không phải là duy nhất (tôi muốn nó, nhưng thời gian nhảy và như vậy, và hệ thống của tôi có lẽ nên thích ghi lại một cái gì đó và sắp xếp thời gian bù đắp sau). Nhưng tôi cho rằng tôi có thể fudge thời gian một chút (những gì là một sự bù đắp của 1/90,000ths của một giây) hoặc chỉ cần thêm trở lại "id" là cột thứ ba trong khóa chính với chỉ số không độc đáo của riêng nó. –

+0

@ScottLamb, tôi sẽ tìm Id. Bạn không bao giờ biết với đồng hồ - đôi khi họ đi ngược lại! Ít nhất ID sẽ cung cấp cho bạn thứ tự chèn vào thực tế để không bị mất. – Ben

Các vấn đề liên quan