Tìm kiếm với các trường phân cấp bằng Solr


Phạm Kim Hoàng
6 năm trước
Hữu ích 5 Chia sẻ Viết bình luận 0
Đã xem 6968

Trong nỗ lực gần đây và đang tiếp tục để biến thế giới thành một nơi tốt đẹp hơn, chúng tôi đã làm việc với Waldo Jaquith lừng lẫy   trong một dự án có tên StateDecoding. Về cơ bản, chúng tôi đang tạo ra các luật dễ dàng tìm kiếm và có thể truy cập bởi người cư sĩ. Tại đây, hãy xem dự án tiền nhiệm,  Virginia Decoding . StateDecoding sẽ tương tự như phiên bản tiền nhiệm nhưng với khả năng tìm kiếm mở rộng và trưởng thành. Và thay vì chỉ luật của tiểu bang Virginia, với StateDecoding, bất kỳ đô thị nào cũng có thể  tải xuống  chỉ mục dự án nguồn mở theo luật riêng của họ và cung cấp cho công dân của họ khả năng hiển thị tốt hơn các quy tắc chi phối họ.

Tuy nhiên, đối với bài đăng này, tôi muốn tập trung vào một trong những người chơi Solr giỏi mà chúng tôi gặp phải liên quan đến bản chất phân cấp của các tài liệu được lập chỉ mục. Luật được chia thành các phần, chương và đoạn và chúng tôi có tài liệu ở mọi cấp độ. Trong Solr của chúng tôi, hệ thống phân cấp này được ghi lại trong một trường có nhãn Phần phân khúc. Vì vậy, ví dụ, đây là 3 ví dụ về trường phần này:

  • <field name="section">30</field> - Một tài liệu có chứa thông tin cụ thể cho phần 30.
  • <field name="section">30.4</field> - Một tài liệu có chứa thông tin cụ thể cho phần 30 chương 4.
  • <field name="section">30.4.15</field> - Tài liệu chứa thông tin cụ thể cho phần 30 chương 4 đoạn 15.

Và mục tiêu của chúng tôi cho lĩnh vực này là nếu bất kỳ ai tìm kiếm một phần luật cụ thể, họ sẽ được cung cấp luật cụ thể nhất theo yêu cầu của họ theo sau là các luật ít cụ thể hơn. Chẳng hạn, nếu người dùng tìm kiếm trên 30.4 “, thì kết quả sẽ chứa các tài liệu cho phần 30, phần 30.4, phần 30.4.15, phần 30.4.16, v.v., và kết quả đầu tiên phải là 30.4. Các tài liệu khác như 40.4 không nên được trả lại.

Ban đầu, chúng tôi đã giải quyết vấn đề với PathHVELyTokenizerFactory.

<fieldname="section"type="path"indexed="true"stored="true"/>

<fieldTypename="path"class="solr.TextField"><analyzer><tokenizerclass="solr.PathHierarchyTokenizerFactory"delimiter="/"/></analyzer></fieldType>

Sử dụng trình phân tích này, đây là cách một số tài liệu mẫu được mã hóa:

30--><30>30.4--><30><30.4>30.4.15--><30><30.4><30.4.15>30.4.16--><30><30.4><30.4.16>30.5--><30><30.5>40.5--><40><40.5>

Và nếu bất cứ ai tìm kiếm một luật, mã thông báo tương tự xảy ra. Vì vậy, nếu ai đó muốn xem xét luật 30.4 ″ thì điều này sẽ được mã hóa thành <30> và <30.4>. Trong trường hợp này, 5 tài liệu đầu tiên phù hợp. Cụ thể, các tài liệu 30.4, 30.4.15 và 30.4.16 đều có hai mã thông báo phù hợp trong khi 30 và 30.5 có một mã thông báo phù hợp. Ngoài ra, do chuẩn hóa độ dài trường, một trận đấu trong trường ngắn hơn - giả sử 30,4 với 2 mã thông báo được tăng cao hơn so với trận đấu trên một trường có 3 mã thông báo như 30.4.15 hoặc 30.4.16.

Tất cả những điều được xem xét, các tài liệu kết quả nên theo thứ tự sau:

  • 30,4
  • 30.4.15
  • 30.4.16
  • 30
  • 30,5

NHƯNG, như số phận sẽ có nó, kế hoạch hoàn hảo của chúng tôi có một lỗ hổng ở đâu đó và các tài liệu trở lại theo thứ tự này:

  • 30.4.15
  • 30,4
  • 30.4.16
  • 30
  • 30,5

Sao, có chuyện gì?! (Các chuyên gia của Solr đang đọc bài này, bạn có biết lý thuyết của chúng ta bị phá vỡ ở đâu không?)

Lý thuyết của chúng tôi thực sự hợp lý, nhưng vấn đề nằm ở vấn đề thực tế là lưu trữ định mức trường. Mỗi trường của mỗi tài liệu được lưu trữ với một phần thông tin bổ sung được gọi là định mức trường. Lý tưởng nhất là định mức trường sẽ có thể chứa thông tin rất cụ thể về thời gian của một tài liệu, nhưng định mức trường càng chính xác thì bạn càng cần nhiều không gian để lưu trữ thông tin đó. Cuối cùng, các nhà phát triển Lucene đã quyết định lưu trữ định mức trường dưới dạng một byte đơn. Điều này có nghĩa là định mức trường có thể lưu trữ chính xác 256 giá trị khác nhau. Và nếu bạn nhìn vào lớp Lucene TFIDFSimilarity (lớp chịu trách nhiệm xử lý các chỉ tiêu trường và tài liệu chấm điểm),  bạn có thể thấy chính xác nơi các giá trị byte này được dịch thành các giá trị dấu phẩy động. Thậm chí còn có một bình luận đưa ra gợi ý về vấn đề của chúng ta có thể là:

Mã hóa sử dụng mantissa ba bit, số mũ năm bit và điểm số mũ 0 ở 15, do đó biểu thị các giá trị từ khoảng 7 × 10 ^ 9 đến 2 × 10 ^ -9 với độ chính xác khoảng một chữ số thập phân. Zero cũng được đại diện. Số âm được làm tròn lên đến không. Các giá trị quá lớn để thể hiện được làm tròn xuống giá trị đại diện lớn nhất. Các giá trị dương quá nhỏ để thể hiện được làm tròn đến giá trị đại diện dương nhỏ nhất.

Do thiếu độ chính xác này, định mức trường cho các trường có độ dài 2 và chiều dài 3 là như nhau! Và đó là lý do tại sao chúng tôi nhận được thứ tự kỳ lạ. Ba tài liệu đầu tiên có cùng số điểm và do đó được trả về theo thứ tự chỉ mục.

Vì vậy, làm thế nào để chúng tôi sửa lỗi này? Chà, một cách sẽ là thực hiện lớp Tương tự của chúng ta sử dụng một chuyển đổi byte-to-float khác với mặc định. Nhưng một trong những mục tiêu chính của dự án cụ thể này là giữ cho bản cài đặt Solr này đơn giản nhất có thể để bất kỳ ai cũng có thể thiết lập nó. Do đó, chúng tôi thấy rằng một giải pháp dễ dàng hơn là sử dụng một kỹ thuật phân tích hơi khác. Hãy xem xét các đoạn lược đồ sau:

<field name="section_descendent" type="descendent_path" indexed="true" stored="true"/>
<field name="section_ancestor" type="ancestor_path" indexed="true" stored="true"/>


<fieldType name="descendent_path" class="solr.TextField">
  <analyzer type="index">
    <tokenizer class="solr.PathHierarchyTokenizerFactory" delimiter="/" />
  </analyzer>
  <analyzer type="query">
    <tokenizer class="solr.KeywordTokenizerFactory" />
  </analyzer>
</fieldType>

<fieldType name="ancestor_path" class="solr.TextField">
  <analyzer type="index">
    <tokenizer class="solr.KeywordTokenizerFactory" />
  </analyzer>
  <analyzer type="query">
    <tokenizer class="solr.PathHierarchyTokenizerFactory" delimiter="/" />
  </analyzer>
</fieldType>

Trong trường hợp này, chúng tôi chia phần thành hai trường, phần_descendent và part_ancestor được mã hóa hơi khác so với trước đây. Sự khác biệt là trong ví dụ trước, PathHVELyTokenizer đã được sử dụng trên cả chỉ mục và truy vấn. Nhưng trong trường hợp này, descendent_path chỉ sử dụng PathHVELyTokenizer tại thời điểm chỉ mục và aneopor_path chỉ sử dụng PathHVELyTokenizer tại thời điểm truy vấn. . tương tự part_ancestor sẽ chỉ khớp các truy vấn cho các phần là tổ tiên của truy vấn - vì vậy 30.4 sẽ khớp với 30.4 và 30, chứ không phải 30.4.15.

Phần cuối cùng của câu đố là xử lý yêu cầu. Ở đây chúng ta chỉ cần sử dụng một trình phân tích cú pháp yêu cầu edismax với một qf (các trường truy vấn) được đặt để bao gồm cả descendent_path aneopor_path. Bây giờ, bất cứ khi nào ai đó truy vấn 30.4, sau đó trong descendent_path họ sẽ nhận được các kết quả khớp vào ngày 30.4, 30.4.15 và 30.4.16; và trong tổ tiên_path họ nhận được các kết quả trùng khớp vào ngày 30.4 và 30. Tài liệu duy nhất phù hợp trong cả hai trường là 30.4, do đó, nó được tăng thêm và xuất hiện ở đầu kết quả tìm kiếm theo sau là các tài liệu khác. Vấn đề được giải quyết!

Bây giờ thật thú vị khi lưu ý rằng chúng tôi đã mất các tài liệu anh chị em (ví dụ 30,5 trong ví dụ này). Cá nhân, điều đó có vẻ tốt đối với tôi, nhưng nếu chúng tôi thực sự muốn nó, tất cả những gì chúng tôi phải làm là một lần nữa bao gồm đường dẫn trường phần gốc được mã hóa trên cả truy vấn và chỉ mục.

Hữu ích 5 Chia sẻ Viết bình luận 0
Đã xem 6968